Update website
This commit is contained in:
parent
4413528994
commit
1d90fbf296
6865 changed files with 1091082 additions and 0 deletions
460
vendor/symfony/http-client/Response/AmpResponse.php
vendored
Normal file
460
vendor/symfony/http-client/Response/AmpResponse.php
vendored
Normal file
|
@ -0,0 +1,460 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Amp\ByteStream\StreamException;
|
||||
use Amp\CancellationTokenSource;
|
||||
use Amp\Coroutine;
|
||||
use Amp\Deferred;
|
||||
use Amp\Http\Client\HttpException;
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Http\Client\Response;
|
||||
use Amp\Loop;
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use Symfony\Component\HttpClient\Chunk\InformationalChunk;
|
||||
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\HttpClientTrait;
|
||||
use Symfony\Component\HttpClient\Internal\AmpBody;
|
||||
use Symfony\Component\HttpClient\Internal\AmpClientState;
|
||||
use Symfony\Component\HttpClient\Internal\Canary;
|
||||
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class AmpResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
use CommonResponseTrait;
|
||||
use TransportResponseTrait;
|
||||
|
||||
private static $nextId = 'a';
|
||||
|
||||
private $multi;
|
||||
private $options;
|
||||
private $onProgress;
|
||||
|
||||
private static $delay;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(AmpClientState $multi, Request $request, array $options, ?LoggerInterface $logger)
|
||||
{
|
||||
$this->multi = $multi;
|
||||
$this->options = &$options;
|
||||
$this->logger = $logger;
|
||||
$this->timeout = $options['timeout'];
|
||||
$this->shouldBuffer = $options['buffer'];
|
||||
|
||||
if ($this->inflate = \extension_loaded('zlib') && !$request->hasHeader('accept-encoding')) {
|
||||
$request->setHeader('Accept-Encoding', 'gzip');
|
||||
}
|
||||
|
||||
$this->initializer = static function (self $response) {
|
||||
return null !== $response->options;
|
||||
};
|
||||
|
||||
$info = &$this->info;
|
||||
$headers = &$this->headers;
|
||||
$canceller = new CancellationTokenSource();
|
||||
$handle = &$this->handle;
|
||||
|
||||
$info['url'] = (string) $request->getUri();
|
||||
$info['http_method'] = $request->getMethod();
|
||||
$info['start_time'] = null;
|
||||
$info['redirect_url'] = null;
|
||||
$info['redirect_time'] = 0.0;
|
||||
$info['redirect_count'] = 0;
|
||||
$info['size_upload'] = 0.0;
|
||||
$info['size_download'] = 0.0;
|
||||
$info['upload_content_length'] = -1.0;
|
||||
$info['download_content_length'] = -1.0;
|
||||
$info['user_data'] = $options['user_data'];
|
||||
$info['max_duration'] = $options['max_duration'];
|
||||
$info['debug'] = '';
|
||||
|
||||
$onProgress = $options['on_progress'] ?? static function () {};
|
||||
$onProgress = $this->onProgress = static function () use (&$info, $onProgress) {
|
||||
$info['total_time'] = microtime(true) - $info['start_time'];
|
||||
$onProgress((int) $info['size_download'], ((int) (1 + $info['download_content_length']) ?: 1) - 1, (array) $info);
|
||||
};
|
||||
|
||||
$pauseDeferred = new Deferred();
|
||||
$pause = new Success();
|
||||
|
||||
$throttleWatcher = null;
|
||||
|
||||
$this->id = $id = self::$nextId++;
|
||||
Loop::defer(static function () use ($request, $multi, &$id, &$info, &$headers, $canceller, &$options, $onProgress, &$handle, $logger, &$pause) {
|
||||
return new Coroutine(self::generateResponse($request, $multi, $id, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause));
|
||||
});
|
||||
|
||||
$info['pause_handler'] = static function (float $duration) use (&$throttleWatcher, &$pauseDeferred, &$pause) {
|
||||
if (null !== $throttleWatcher) {
|
||||
Loop::cancel($throttleWatcher);
|
||||
}
|
||||
|
||||
$pause = $pauseDeferred->promise();
|
||||
|
||||
if ($duration <= 0) {
|
||||
$deferred = $pauseDeferred;
|
||||
$pauseDeferred = new Deferred();
|
||||
$deferred->resolve();
|
||||
} else {
|
||||
$throttleWatcher = Loop::delay(ceil(1000 * $duration), static function () use (&$pauseDeferred) {
|
||||
$deferred = $pauseDeferred;
|
||||
$pauseDeferred = new Deferred();
|
||||
$deferred->resolve();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$multi->lastTimeout = null;
|
||||
$multi->openHandles[$id] = $id;
|
||||
++$multi->responseCount;
|
||||
|
||||
$this->canary = new Canary(static function () use ($canceller, $multi, $id) {
|
||||
$canceller->cancel();
|
||||
unset($multi->openHandles[$id], $multi->handlesActivity[$id]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
return null !== $type ? $this->info[$type] ?? null : $this->info;
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
try {
|
||||
$this->doDestruct();
|
||||
} finally {
|
||||
// Clear the DNS cache when all requests completed
|
||||
if (0 >= --$this->multi->responseCount) {
|
||||
$this->multi->responseCount = 0;
|
||||
$this->multi->dnsCache = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private static function schedule(self $response, array &$runningResponses): void
|
||||
{
|
||||
if (isset($runningResponses[0])) {
|
||||
$runningResponses[0][1][$response->id] = $response;
|
||||
} else {
|
||||
$runningResponses[0] = [$response->multi, [$response->id => $response]];
|
||||
}
|
||||
|
||||
if (!isset($response->multi->openHandles[$response->id])) {
|
||||
$response->multi->handlesActivity[$response->id][] = null;
|
||||
$response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param AmpClientState $multi
|
||||
*/
|
||||
private static function perform(ClientState $multi, ?array &$responses = null): void
|
||||
{
|
||||
if ($responses) {
|
||||
foreach ($responses as $response) {
|
||||
try {
|
||||
if ($response->info['start_time']) {
|
||||
$response->info['total_time'] = microtime(true) - $response->info['start_time'];
|
||||
($response->onProgress)();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$multi->handlesActivity[$response->id][] = null;
|
||||
$multi->handlesActivity[$response->id][] = $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param AmpClientState $multi
|
||||
*/
|
||||
private static function select(ClientState $multi, float $timeout): int
|
||||
{
|
||||
$timeout += microtime(true);
|
||||
self::$delay = Loop::defer(static function () use ($timeout) {
|
||||
if (0 < $timeout -= microtime(true)) {
|
||||
self::$delay = Loop::delay(ceil(1000 * $timeout), [Loop::class, 'stop']);
|
||||
} else {
|
||||
Loop::stop();
|
||||
}
|
||||
});
|
||||
|
||||
Loop::run();
|
||||
|
||||
return null === self::$delay ? 1 : 0;
|
||||
}
|
||||
|
||||
private static function generateResponse(Request $request, AmpClientState $multi, string $id, array &$info, array &$headers, CancellationTokenSource $canceller, array &$options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause)
|
||||
{
|
||||
$request->setInformationalResponseHandler(static function (Response $response) use ($multi, $id, &$info, &$headers) {
|
||||
self::addResponseHeaders($response, $info, $headers);
|
||||
$multi->handlesActivity[$id][] = new InformationalChunk($response->getStatus(), $response->getHeaders());
|
||||
self::stopLoop();
|
||||
});
|
||||
|
||||
try {
|
||||
/* @var Response $response */
|
||||
if (null === $response = yield from self::getPushedResponse($request, $multi, $info, $headers, $options, $logger)) {
|
||||
$logger && $logger->info(sprintf('Request: "%s %s"', $info['http_method'], $info['url']));
|
||||
|
||||
$response = yield from self::followRedirects($request, $multi, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause);
|
||||
}
|
||||
|
||||
$options = null;
|
||||
|
||||
$multi->handlesActivity[$id][] = new FirstChunk();
|
||||
|
||||
if ('HEAD' === $response->getRequest()->getMethod() || \in_array($info['http_code'], [204, 304], true)) {
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
self::stopLoop();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($response->hasHeader('content-length')) {
|
||||
$info['download_content_length'] = (float) $response->getHeader('content-length');
|
||||
}
|
||||
|
||||
$body = $response->getBody();
|
||||
|
||||
while (true) {
|
||||
self::stopLoop();
|
||||
|
||||
yield $pause;
|
||||
|
||||
if (null === $data = yield $body->read()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$info['size_download'] += \strlen($data);
|
||||
$multi->handlesActivity[$id][] = $data;
|
||||
}
|
||||
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
} catch (\Throwable $e) {
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = $e;
|
||||
} finally {
|
||||
$info['download_content_length'] = $info['size_download'];
|
||||
}
|
||||
|
||||
self::stopLoop();
|
||||
}
|
||||
|
||||
private static function followRedirects(Request $originRequest, AmpClientState $multi, array &$info, array &$headers, CancellationTokenSource $canceller, array $options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause)
|
||||
{
|
||||
yield $pause;
|
||||
|
||||
$originRequest->setBody(new AmpBody($options['body'], $info, $onProgress));
|
||||
$response = yield $multi->request($options, $originRequest, $canceller->getToken(), $info, $onProgress, $handle);
|
||||
$previousUrl = null;
|
||||
|
||||
while (true) {
|
||||
self::addResponseHeaders($response, $info, $headers);
|
||||
$status = $response->getStatus();
|
||||
|
||||
if (!\in_array($status, [301, 302, 303, 307, 308], true) || null === $location = $response->getHeader('location')) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$urlResolver = new class() {
|
||||
use HttpClientTrait {
|
||||
parseUrl as public;
|
||||
resolveUrl as public;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
$previousUrl = $previousUrl ?? $urlResolver::parseUrl($info['url']);
|
||||
$location = $urlResolver::parseUrl($location);
|
||||
$location = $urlResolver::resolveUrl($location, $previousUrl);
|
||||
$info['redirect_url'] = implode('', $location);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if (0 >= $options['max_redirects'] || $info['redirect_count'] >= $options['max_redirects']) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$logger && $logger->info(sprintf('Redirecting: "%s %s"', $status, $info['url']));
|
||||
|
||||
try {
|
||||
// Discard body of redirects
|
||||
while (null !== yield $response->getBody()->read()) {
|
||||
}
|
||||
} catch (HttpException|StreamException $e) {
|
||||
// Ignore streaming errors on previous responses
|
||||
}
|
||||
|
||||
++$info['redirect_count'];
|
||||
$info['url'] = $info['redirect_url'];
|
||||
$info['redirect_url'] = null;
|
||||
$previousUrl = $location;
|
||||
|
||||
$request = new Request($info['url'], $info['http_method']);
|
||||
$request->setProtocolVersions($originRequest->getProtocolVersions());
|
||||
$request->setTcpConnectTimeout($originRequest->getTcpConnectTimeout());
|
||||
$request->setTlsHandshakeTimeout($originRequest->getTlsHandshakeTimeout());
|
||||
$request->setTransferTimeout($originRequest->getTransferTimeout());
|
||||
|
||||
if (\in_array($status, [301, 302, 303], true)) {
|
||||
$originRequest->removeHeader('transfer-encoding');
|
||||
$originRequest->removeHeader('content-length');
|
||||
$originRequest->removeHeader('content-type');
|
||||
|
||||
// Do like curl and browsers: turn POST to GET on 301, 302 and 303
|
||||
if ('POST' === $response->getRequest()->getMethod() || 303 === $status) {
|
||||
$info['http_method'] = 'HEAD' === $response->getRequest()->getMethod() ? 'HEAD' : 'GET';
|
||||
$request->setMethod($info['http_method']);
|
||||
}
|
||||
} else {
|
||||
$request->setBody(AmpBody::rewind($response->getRequest()->getBody()));
|
||||
}
|
||||
|
||||
foreach ($originRequest->getRawHeaders() as [$name, $value]) {
|
||||
$request->addHeader($name, $value);
|
||||
}
|
||||
|
||||
if ($request->getUri()->getAuthority() !== $originRequest->getUri()->getAuthority()) {
|
||||
$request->removeHeader('authorization');
|
||||
$request->removeHeader('cookie');
|
||||
$request->removeHeader('host');
|
||||
}
|
||||
|
||||
yield $pause;
|
||||
|
||||
$response = yield $multi->request($options, $request, $canceller->getToken(), $info, $onProgress, $handle);
|
||||
$info['redirect_time'] = microtime(true) - $info['start_time'];
|
||||
}
|
||||
}
|
||||
|
||||
private static function addResponseHeaders(Response $response, array &$info, array &$headers): void
|
||||
{
|
||||
$info['http_code'] = $response->getStatus();
|
||||
|
||||
if ($headers) {
|
||||
$info['debug'] .= "< \r\n";
|
||||
$headers = [];
|
||||
}
|
||||
|
||||
$h = sprintf('HTTP/%s %s %s', $response->getProtocolVersion(), $response->getStatus(), $response->getReason());
|
||||
$info['debug'] .= "< {$h}\r\n";
|
||||
$info['response_headers'][] = $h;
|
||||
|
||||
foreach ($response->getRawHeaders() as [$name, $value]) {
|
||||
$headers[strtolower($name)][] = $value;
|
||||
$h = $name.': '.$value;
|
||||
$info['debug'] .= "< {$h}\r\n";
|
||||
$info['response_headers'][] = $h;
|
||||
}
|
||||
|
||||
$info['debug'] .= "< \r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts pushed responses only if their headers related to authentication match the request.
|
||||
*/
|
||||
private static function getPushedResponse(Request $request, AmpClientState $multi, array &$info, array &$headers, array $options, ?LoggerInterface $logger)
|
||||
{
|
||||
if ('' !== $options['body']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$authority = $request->getUri()->getAuthority();
|
||||
|
||||
foreach ($multi->pushedResponses[$authority] ?? [] as $i => [$pushedUrl, $pushDeferred, $pushedRequest, $pushedResponse, $parentOptions]) {
|
||||
if ($info['url'] !== $pushedUrl || $info['http_method'] !== $pushedRequest->getMethod()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($parentOptions as $k => $v) {
|
||||
if ($options[$k] !== $v) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) {
|
||||
if ($pushedRequest->getHeaderArray($k) !== $request->getHeaderArray($k)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$response = yield $pushedResponse;
|
||||
|
||||
foreach ($response->getHeaderArray('vary') as $vary) {
|
||||
foreach (preg_split('/\s*+,\s*+/', $vary) as $v) {
|
||||
if ('*' === $v || ($pushedRequest->getHeaderArray($v) !== $request->getHeaderArray($v) && 'accept-encoding' !== strtolower($v))) {
|
||||
$logger && $logger->debug(sprintf('Skipping pushed response: "%s"', $info['url']));
|
||||
continue 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pushDeferred->resolve();
|
||||
$logger && $logger->debug(sprintf('Accepting pushed response: "%s %s"', $info['http_method'], $info['url']));
|
||||
self::addResponseHeaders($response, $info, $headers);
|
||||
unset($multi->pushedResponses[$authority][$i]);
|
||||
|
||||
if (!$multi->pushedResponses[$authority]) {
|
||||
unset($multi->pushedResponses[$authority]);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
private static function stopLoop(): void
|
||||
{
|
||||
if (null !== self::$delay) {
|
||||
Loop::cancel(self::$delay);
|
||||
self::$delay = null;
|
||||
}
|
||||
|
||||
Loop::defer([Loop::class, 'stop']);
|
||||
}
|
||||
}
|
195
vendor/symfony/http-client/Response/AsyncContext.php
vendored
Normal file
195
vendor/symfony/http-client/Response/AsyncContext.php
vendored
Normal file
|
@ -0,0 +1,195 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Symfony\Component\HttpClient\Chunk\DataChunk;
|
||||
use Symfony\Component\HttpClient\Chunk\LastChunk;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A DTO to work with AsyncResponse.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class AsyncContext
|
||||
{
|
||||
private $passthru;
|
||||
private $client;
|
||||
private $response;
|
||||
private $info = [];
|
||||
private $content;
|
||||
private $offset;
|
||||
|
||||
public function __construct(&$passthru, HttpClientInterface $client, ResponseInterface &$response, array &$info, $content, int $offset)
|
||||
{
|
||||
$this->passthru = &$passthru;
|
||||
$this->client = $client;
|
||||
$this->response = &$response;
|
||||
$this->info = &$info;
|
||||
$this->content = $content;
|
||||
$this->offset = $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP status without consuming the response.
|
||||
*/
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->response->getInfo('http_code');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers without consuming the response.
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
foreach ($this->response->getInfo('response_headers') as $h) {
|
||||
if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([123456789]\d\d)(?: |$)#', $h, $m)) {
|
||||
$headers = [];
|
||||
} elseif (2 === \count($m = explode(':', $h, 2))) {
|
||||
$headers[strtolower($m[0])][] = ltrim($m[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource|null The PHP stream resource where the content is buffered, if it is
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new chunk of content.
|
||||
*/
|
||||
public function createChunk(string $data): ChunkInterface
|
||||
{
|
||||
return new DataChunk($this->offset, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the request for the given number of seconds.
|
||||
*/
|
||||
public function pause(float $duration): void
|
||||
{
|
||||
if (\is_callable($pause = $this->response->getInfo('pause_handler'))) {
|
||||
$pause($duration);
|
||||
} elseif (0 < $duration) {
|
||||
usleep((int) (1E6 * $duration));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the request and returns the last chunk to yield.
|
||||
*/
|
||||
public function cancel(): ChunkInterface
|
||||
{
|
||||
$this->info['canceled'] = true;
|
||||
$this->info['error'] = 'Response has been canceled.';
|
||||
$this->response->cancel();
|
||||
|
||||
return new LastChunk();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current info of the response.
|
||||
*/
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
if (null !== $type) {
|
||||
return $this->info[$type] ?? $this->response->getInfo($type);
|
||||
}
|
||||
|
||||
return $this->info + $this->response->getInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches an info to the response.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setInfo(string $type, $value): self
|
||||
{
|
||||
if ('canceled' === $type && $value !== $this->info['canceled']) {
|
||||
throw new \LogicException('You cannot set the "canceled" info directly.');
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
unset($this->info[$type]);
|
||||
} else {
|
||||
$this->info[$type] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently processed response.
|
||||
*/
|
||||
public function getResponse(): ResponseInterface
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the currently processed response by doing a new request.
|
||||
*/
|
||||
public function replaceRequest(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
$this->info['previous_info'][] = $info = $this->response->getInfo();
|
||||
if (null !== $onProgress = $options['on_progress'] ?? null) {
|
||||
$thisInfo = &$this->info;
|
||||
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) {
|
||||
$onProgress($dlNow, $dlSize, $thisInfo + $info);
|
||||
};
|
||||
}
|
||||
if (0 < ($info['max_duration'] ?? 0) && 0 < ($info['total_time'] ?? 0)) {
|
||||
if (0 >= $options['max_duration'] = $info['max_duration'] - $info['total_time']) {
|
||||
throw new TransportException(sprintf('Max duration was reached for "%s".', $info['url']));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response = $this->client->request($method, $url, ['buffer' => false] + $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the currently processed response by another one.
|
||||
*/
|
||||
public function replaceResponse(ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$this->info['previous_info'][] = $this->response->getInfo();
|
||||
|
||||
return $this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces or removes the chunk filter iterator.
|
||||
*
|
||||
* @param ?callable(ChunkInterface, self): ?\Iterator $passthru
|
||||
*/
|
||||
public function passthru(?callable $passthru = null): void
|
||||
{
|
||||
$this->passthru = $passthru ?? static function ($chunk, $context) {
|
||||
$context->passthru = null;
|
||||
|
||||
yield $chunk;
|
||||
};
|
||||
}
|
||||
}
|
473
vendor/symfony/http-client/Response/AsyncResponse.php
vendored
Normal file
473
vendor/symfony/http-client/Response/AsyncResponse.php
vendored
Normal file
|
@ -0,0 +1,473 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Symfony\Component\HttpClient\Chunk\ErrorChunk;
|
||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use Symfony\Component\HttpClient\Chunk\LastChunk;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Provides a single extension point to process a response's content stream.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class AsyncResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
use CommonResponseTrait;
|
||||
|
||||
private const FIRST_CHUNK_YIELDED = 1;
|
||||
private const LAST_CHUNK_YIELDED = 2;
|
||||
|
||||
private $client;
|
||||
private $response;
|
||||
private $info = ['canceled' => false];
|
||||
private $passthru;
|
||||
private $stream;
|
||||
private $yieldedState;
|
||||
|
||||
/**
|
||||
* @param ?callable(ChunkInterface, AsyncContext): ?\Iterator $passthru
|
||||
*/
|
||||
public function __construct(HttpClientInterface $client, string $method, string $url, array $options, ?callable $passthru = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->shouldBuffer = $options['buffer'] ?? true;
|
||||
|
||||
if (null !== $onProgress = $options['on_progress'] ?? null) {
|
||||
$thisInfo = &$this->info;
|
||||
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) {
|
||||
$onProgress($dlNow, $dlSize, $thisInfo + $info);
|
||||
};
|
||||
}
|
||||
$this->response = $client->request($method, $url, ['buffer' => false] + $options);
|
||||
$this->passthru = $passthru;
|
||||
$this->initializer = static function (self $response, ?float $timeout = null) {
|
||||
if (null === $response->shouldBuffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
foreach (self::stream([$response], $timeout) as $chunk) {
|
||||
if ($chunk->isTimeout() && $response->passthru) {
|
||||
// Timeouts thrown during initialization are transport errors
|
||||
foreach (self::passthru($response->client, $response, new ErrorChunk($response->offset, new TransportException($chunk->getError()))) as $chunk) {
|
||||
if ($chunk->isFirst()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
if ($chunk->isFirst()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if (\array_key_exists('user_data', $options)) {
|
||||
$this->info['user_data'] = $options['user_data'];
|
||||
}
|
||||
if (\array_key_exists('max_duration', $options)) {
|
||||
$this->info['max_duration'] = $options['max_duration'];
|
||||
}
|
||||
}
|
||||
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
if ($this->initializer) {
|
||||
self::initialize($this);
|
||||
}
|
||||
|
||||
return $this->response->getStatusCode();
|
||||
}
|
||||
|
||||
public function getHeaders(bool $throw = true): array
|
||||
{
|
||||
if ($this->initializer) {
|
||||
self::initialize($this);
|
||||
}
|
||||
|
||||
$headers = $this->response->getHeaders(false);
|
||||
|
||||
if ($throw) {
|
||||
$this->checkStatusCode();
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
if (null !== $type) {
|
||||
return $this->info[$type] ?? $this->response->getInfo($type);
|
||||
}
|
||||
|
||||
return $this->info + $this->response->getInfo();
|
||||
}
|
||||
|
||||
public function toStream(bool $throw = true)
|
||||
{
|
||||
if ($throw) {
|
||||
// Ensure headers arrived
|
||||
$this->getHeaders(true);
|
||||
}
|
||||
|
||||
$handle = function () {
|
||||
$stream = $this->response instanceof StreamableInterface ? $this->response->toStream(false) : StreamWrapper::createResource($this->response);
|
||||
|
||||
return stream_get_meta_data($stream)['wrapper_data']->stream_cast(\STREAM_CAST_FOR_SELECT);
|
||||
};
|
||||
|
||||
$stream = StreamWrapper::createResource($this);
|
||||
stream_get_meta_data($stream)['wrapper_data']
|
||||
->bindHandles($handle, $this->content);
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
public function cancel(): void
|
||||
{
|
||||
if ($this->info['canceled']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info['canceled'] = true;
|
||||
$this->info['error'] = 'Response has been canceled.';
|
||||
$this->close();
|
||||
$client = $this->client;
|
||||
$this->client = null;
|
||||
|
||||
if (!$this->passthru) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach (self::passthru($client, $this, new LastChunk()) as $chunk) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
$this->passthru = null;
|
||||
} catch (ExceptionInterface $e) {
|
||||
// ignore any errors when canceling
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$httpException = null;
|
||||
|
||||
if ($this->initializer && null === $this->getInfo('error')) {
|
||||
try {
|
||||
self::initialize($this, -0.0);
|
||||
$this->getHeaders(true);
|
||||
} catch (HttpExceptionInterface $httpException) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->passthru && null === $this->getInfo('error')) {
|
||||
$this->info['canceled'] = true;
|
||||
|
||||
try {
|
||||
foreach (self::passthru($this->client, $this, new LastChunk()) as $chunk) {
|
||||
// no-op
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
// ignore any errors when destructing
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $httpException) {
|
||||
throw $httpException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function stream(iterable $responses, ?float $timeout = null, ?string $class = null): \Generator
|
||||
{
|
||||
while ($responses) {
|
||||
$wrappedResponses = [];
|
||||
$asyncMap = new \SplObjectStorage();
|
||||
$client = null;
|
||||
|
||||
foreach ($responses as $r) {
|
||||
if (!$r instanceof self) {
|
||||
throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', $class ?? static::class, get_debug_type($r)));
|
||||
}
|
||||
|
||||
if (null !== $e = $r->info['error'] ?? null) {
|
||||
yield $r => $chunk = new ErrorChunk($r->offset, new TransportException($e));
|
||||
$chunk->didThrow() ?: $chunk->getContent();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $client) {
|
||||
$client = $r->client;
|
||||
} elseif ($r->client !== $client) {
|
||||
throw new TransportException('Cannot stream AsyncResponse objects with many clients.');
|
||||
}
|
||||
|
||||
$asyncMap[$r->response] = $r;
|
||||
$wrappedResponses[] = $r->response;
|
||||
|
||||
if ($r->stream) {
|
||||
yield from self::passthruStream($response = $r->response, $r, new FirstChunk(), $asyncMap);
|
||||
|
||||
if (!isset($asyncMap[$response])) {
|
||||
array_pop($wrappedResponses);
|
||||
}
|
||||
|
||||
if ($r->response !== $response && !isset($asyncMap[$r->response])) {
|
||||
$asyncMap[$r->response] = $r;
|
||||
$wrappedResponses[] = $r->response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$client || !$wrappedResponses) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($client->stream($wrappedResponses, $timeout) as $response => $chunk) {
|
||||
$r = $asyncMap[$response];
|
||||
|
||||
if (null === $chunk->getError()) {
|
||||
if ($chunk->isFirst()) {
|
||||
// Ensure no exception is thrown on destruct for the wrapped response
|
||||
$r->response->getStatusCode();
|
||||
} elseif (0 === $r->offset && null === $r->content && $chunk->isLast()) {
|
||||
$r->content = fopen('php://memory', 'w+');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$r->passthru) {
|
||||
if (null !== $chunk->getError() || $chunk->isLast()) {
|
||||
unset($asyncMap[$response]);
|
||||
} elseif (null !== $r->content && '' !== ($content = $chunk->getContent()) && \strlen($content) !== fwrite($r->content, $content)) {
|
||||
$chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content))));
|
||||
$r->info['error'] = $chunk->getError();
|
||||
$r->response->cancel();
|
||||
}
|
||||
|
||||
yield $r => $chunk;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null !== $chunk->getError()) {
|
||||
// no-op
|
||||
} elseif ($chunk->isFirst()) {
|
||||
$r->yieldedState = self::FIRST_CHUNK_YIELDED;
|
||||
} elseif (self::FIRST_CHUNK_YIELDED !== $r->yieldedState && null === $chunk->getInformationalStatus()) {
|
||||
throw new \LogicException(sprintf('Instance of "%s" is already consumed and cannot be managed by "%s". A decorated client should not call any of the response\'s methods in its "request()" method.', get_debug_type($response), $class ?? static::class));
|
||||
}
|
||||
|
||||
foreach (self::passthru($r->client, $r, $chunk, $asyncMap) as $chunk) {
|
||||
yield $r => $chunk;
|
||||
}
|
||||
|
||||
if ($r->response !== $response && isset($asyncMap[$response])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $chunk->getError() && $chunk->isLast()) {
|
||||
$r->yieldedState = self::LAST_CHUNK_YIELDED;
|
||||
}
|
||||
if (null === $chunk->getError() && self::LAST_CHUNK_YIELDED !== $r->yieldedState && $r->response === $response && null !== $r->client) {
|
||||
throw new \LogicException('A chunk passthru must yield an "isLast()" chunk before ending a stream.');
|
||||
}
|
||||
|
||||
$responses = [];
|
||||
foreach ($asyncMap as $response) {
|
||||
$r = $asyncMap[$response];
|
||||
|
||||
if (null !== $r->client) {
|
||||
$responses[] = $asyncMap[$response];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SplObjectStorage<ResponseInterface, AsyncResponse>|null $asyncMap
|
||||
*/
|
||||
private static function passthru(HttpClientInterface $client, self $r, ChunkInterface $chunk, ?\SplObjectStorage $asyncMap = null): \Generator
|
||||
{
|
||||
$r->stream = null;
|
||||
$response = $r->response;
|
||||
$context = new AsyncContext($r->passthru, $client, $r->response, $r->info, $r->content, $r->offset);
|
||||
if (null === $stream = ($r->passthru)($chunk, $context)) {
|
||||
if ($r->response === $response && (null !== $chunk->getError() || $chunk->isLast())) {
|
||||
throw new \LogicException('A chunk passthru cannot swallow the last chunk.');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$stream instanceof \Iterator) {
|
||||
throw new \LogicException(sprintf('A chunk passthru must return an "Iterator", "%s" returned.', get_debug_type($stream)));
|
||||
}
|
||||
$r->stream = $stream;
|
||||
|
||||
yield from self::passthruStream($response, $r, null, $asyncMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SplObjectStorage<ResponseInterface, AsyncResponse>|null $asyncMap
|
||||
*/
|
||||
private static function passthruStream(ResponseInterface $response, self $r, ?ChunkInterface $chunk, ?\SplObjectStorage $asyncMap): \Generator
|
||||
{
|
||||
while (true) {
|
||||
try {
|
||||
if (null !== $chunk && $r->stream) {
|
||||
$r->stream->next();
|
||||
}
|
||||
|
||||
if (!$r->stream || !$r->stream->valid() || !$r->stream) {
|
||||
$r->stream = null;
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
unset($asyncMap[$response]);
|
||||
$r->stream = null;
|
||||
$r->info['error'] = $e->getMessage();
|
||||
$r->response->cancel();
|
||||
|
||||
yield $r => $chunk = new ErrorChunk($r->offset, $e);
|
||||
$chunk->didThrow() ?: $chunk->getContent();
|
||||
break;
|
||||
}
|
||||
|
||||
$chunk = $r->stream->current();
|
||||
|
||||
if (!$chunk instanceof ChunkInterface) {
|
||||
throw new \LogicException(sprintf('A chunk passthru must yield instances of "%s", "%s" yielded.', ChunkInterface::class, get_debug_type($chunk)));
|
||||
}
|
||||
|
||||
if (null !== $chunk->getError()) {
|
||||
// no-op
|
||||
} elseif ($chunk->isFirst()) {
|
||||
$e = $r->openBuffer();
|
||||
|
||||
yield $r => $chunk;
|
||||
|
||||
if ($r->initializer && null === $r->getInfo('error')) {
|
||||
// Ensure the HTTP status code is always checked
|
||||
$r->getHeaders(true);
|
||||
}
|
||||
|
||||
if (null === $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$r->response->cancel();
|
||||
$chunk = new ErrorChunk($r->offset, $e);
|
||||
} elseif ('' !== $content = $chunk->getContent()) {
|
||||
if (null !== $r->shouldBuffer) {
|
||||
throw new \LogicException('A chunk passthru must yield an "isFirst()" chunk before any content chunk.');
|
||||
}
|
||||
|
||||
if (null !== $r->content && \strlen($content) !== fwrite($r->content, $content)) {
|
||||
$chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content))));
|
||||
$r->info['error'] = $chunk->getError();
|
||||
$r->response->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $chunk->getError() || $chunk->isLast()) {
|
||||
$stream = $r->stream;
|
||||
$r->stream = null;
|
||||
unset($asyncMap[$response]);
|
||||
}
|
||||
|
||||
if (null === $chunk->getError()) {
|
||||
$r->offset += \strlen($content);
|
||||
|
||||
yield $r => $chunk;
|
||||
|
||||
if (!$chunk->isLast()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stream->next();
|
||||
|
||||
if ($stream->valid()) {
|
||||
throw new \LogicException('A chunk passthru cannot yield after an "isLast()" chunk.');
|
||||
}
|
||||
|
||||
$r->passthru = null;
|
||||
} else {
|
||||
if ($chunk instanceof ErrorChunk) {
|
||||
$chunk->didThrow(false);
|
||||
} else {
|
||||
try {
|
||||
$chunk = new ErrorChunk($chunk->getOffset(), !$chunk->isTimeout() ?: $chunk->getError());
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$chunk = new ErrorChunk($chunk->getOffset(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
yield $r => $chunk;
|
||||
$chunk->didThrow() ?: $chunk->getContent();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function openBuffer(): ?\Throwable
|
||||
{
|
||||
if (null === $shouldBuffer = $this->shouldBuffer) {
|
||||
throw new \LogicException('A chunk passthru cannot yield more than one "isFirst()" chunk.');
|
||||
}
|
||||
|
||||
$e = $this->shouldBuffer = null;
|
||||
|
||||
if ($shouldBuffer instanceof \Closure) {
|
||||
try {
|
||||
$shouldBuffer = $shouldBuffer($this->getHeaders(false));
|
||||
|
||||
if (null !== $e = $this->response->getInfo('error')) {
|
||||
throw new TransportException($e);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->info['error'] = $e->getMessage();
|
||||
$this->response->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if (true === $shouldBuffer) {
|
||||
$this->content = fopen('php://temp', 'w+');
|
||||
} elseif (\is_resource($shouldBuffer)) {
|
||||
$this->content = $shouldBuffer;
|
||||
}
|
||||
|
||||
return $e;
|
||||
}
|
||||
|
||||
private function close(): void
|
||||
{
|
||||
$this->response->cancel();
|
||||
}
|
||||
}
|
185
vendor/symfony/http-client/Response/CommonResponseTrait.php
vendored
Normal file
185
vendor/symfony/http-client/Response/CommonResponseTrait.php
vendored
Normal file
|
@ -0,0 +1,185 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Symfony\Component\HttpClient\Exception\ClientException;
|
||||
use Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use Symfony\Component\HttpClient\Exception\RedirectionException;
|
||||
use Symfony\Component\HttpClient\Exception\ServerException;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
|
||||
/**
|
||||
* Implements common logic for response classes.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait CommonResponseTrait
|
||||
{
|
||||
/**
|
||||
* @var callable|null A callback that tells whether we're waiting for response headers
|
||||
*/
|
||||
private $initializer;
|
||||
private $shouldBuffer;
|
||||
private $content;
|
||||
private $offset = 0;
|
||||
private $jsonData;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContent(bool $throw = true): string
|
||||
{
|
||||
if ($this->initializer) {
|
||||
self::initialize($this);
|
||||
}
|
||||
|
||||
if ($throw) {
|
||||
$this->checkStatusCode();
|
||||
}
|
||||
|
||||
if (null === $this->content) {
|
||||
$content = null;
|
||||
|
||||
foreach (self::stream([$this]) as $chunk) {
|
||||
if (!$chunk->isLast()) {
|
||||
$content .= $chunk->getContent();
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $content) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
if (null === $this->content) {
|
||||
throw new TransportException('Cannot get the content of the response twice: buffering is disabled.');
|
||||
}
|
||||
} else {
|
||||
foreach (self::stream([$this]) as $chunk) {
|
||||
// Chunks are buffered in $this->content already
|
||||
}
|
||||
}
|
||||
|
||||
rewind($this->content);
|
||||
|
||||
return stream_get_contents($this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toArray(bool $throw = true): array
|
||||
{
|
||||
if ('' === $content = $this->getContent($throw)) {
|
||||
throw new JsonException('Response body is empty.');
|
||||
}
|
||||
|
||||
if (null !== $this->jsonData) {
|
||||
return $this->jsonData;
|
||||
}
|
||||
|
||||
try {
|
||||
$content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0));
|
||||
} catch (\JsonException $e) {
|
||||
throw new JsonException($e->getMessage().sprintf(' for "%s".', $this->getInfo('url')), $e->getCode());
|
||||
}
|
||||
|
||||
if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new JsonException(json_last_error_msg().sprintf(' for "%s".', $this->getInfo('url')), json_last_error());
|
||||
}
|
||||
|
||||
if (!\is_array($content)) {
|
||||
throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned for "%s".', get_debug_type($content), $this->getInfo('url')));
|
||||
}
|
||||
|
||||
if (null !== $this->content) {
|
||||
// Option "buffer" is true
|
||||
return $this->jsonData = $content;
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toStream(bool $throw = true)
|
||||
{
|
||||
if ($throw) {
|
||||
// Ensure headers arrived
|
||||
$this->getHeaders($throw);
|
||||
}
|
||||
|
||||
$stream = StreamWrapper::createResource($this);
|
||||
stream_get_meta_data($stream)['wrapper_data']
|
||||
->bindHandles($this->handle, $this->content);
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the response and all its network handles.
|
||||
*/
|
||||
abstract protected function close(): void;
|
||||
|
||||
private static function initialize(self $response): void
|
||||
{
|
||||
if (null !== $response->getInfo('error')) {
|
||||
throw new TransportException($response->getInfo('error'));
|
||||
}
|
||||
|
||||
try {
|
||||
if (($response->initializer)($response, -0.0)) {
|
||||
foreach (self::stream([$response], -0.0) as $chunk) {
|
||||
if ($chunk->isFirst()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Persist timeouts thrown during initialization
|
||||
$response->info['error'] = $e->getMessage();
|
||||
$response->close();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$response->initializer = null;
|
||||
}
|
||||
|
||||
private function checkStatusCode()
|
||||
{
|
||||
$code = $this->getInfo('http_code');
|
||||
|
||||
if (500 <= $code) {
|
||||
throw new ServerException($this);
|
||||
}
|
||||
|
||||
if (400 <= $code) {
|
||||
throw new ClientException($this);
|
||||
}
|
||||
|
||||
if (300 <= $code) {
|
||||
throw new RedirectionException($this);
|
||||
}
|
||||
}
|
||||
}
|
472
vendor/symfony/http-client/Response/CurlResponse.php
vendored
Normal file
472
vendor/symfony/http-client/Response/CurlResponse.php
vendored
Normal file
|
@ -0,0 +1,472 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use Symfony\Component\HttpClient\Chunk\InformationalChunk;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Internal\Canary;
|
||||
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||
use Symfony\Component\HttpClient\Internal\CurlClientState;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CurlResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
use CommonResponseTrait {
|
||||
getContent as private doGetContent;
|
||||
}
|
||||
use TransportResponseTrait;
|
||||
|
||||
private $multi;
|
||||
private $debugBuffer;
|
||||
|
||||
/**
|
||||
* @param \CurlHandle|resource|string $ch
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(CurlClientState $multi, $ch, ?array $options = null, ?LoggerInterface $logger = null, string $method = 'GET', ?callable $resolveRedirect = null, ?int $curlVersion = null)
|
||||
{
|
||||
$this->multi = $multi;
|
||||
|
||||
if (\is_resource($ch) || $ch instanceof \CurlHandle) {
|
||||
$this->handle = $ch;
|
||||
$this->debugBuffer = fopen('php://temp', 'w+');
|
||||
if (0x074000 === $curlVersion) {
|
||||
fwrite($this->debugBuffer, 'Due to a bug in curl 7.64.0, the debug log is disabled; use another version to work around the issue.');
|
||||
} else {
|
||||
curl_setopt($ch, \CURLOPT_VERBOSE, true);
|
||||
curl_setopt($ch, \CURLOPT_STDERR, $this->debugBuffer);
|
||||
}
|
||||
} else {
|
||||
$this->info['url'] = $ch;
|
||||
$ch = $this->handle;
|
||||
}
|
||||
|
||||
$this->id = $id = (int) $ch;
|
||||
$this->logger = $logger;
|
||||
$this->shouldBuffer = $options['buffer'] ?? true;
|
||||
$this->timeout = $options['timeout'] ?? null;
|
||||
$this->info['http_method'] = $method;
|
||||
$this->info['user_data'] = $options['user_data'] ?? null;
|
||||
$this->info['max_duration'] = $options['max_duration'] ?? null;
|
||||
$this->info['start_time'] = $this->info['start_time'] ?? microtime(true);
|
||||
$info = &$this->info;
|
||||
$headers = &$this->headers;
|
||||
$debugBuffer = $this->debugBuffer;
|
||||
|
||||
if (!$info['response_headers']) {
|
||||
// Used to keep track of what we're waiting for
|
||||
curl_setopt($ch, \CURLOPT_PRIVATE, \in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], true) && 1.0 < (float) ($options['http_version'] ?? 1.1) ? 'H2' : 'H0'); // H = headers + retry counter
|
||||
}
|
||||
|
||||
curl_setopt($ch, \CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger): int {
|
||||
return self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger);
|
||||
});
|
||||
|
||||
if (null === $options) {
|
||||
// Pushed response: buffer until requested
|
||||
curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int {
|
||||
$multi->handlesActivity[$id][] = $data;
|
||||
curl_pause($ch, \CURLPAUSE_RECV);
|
||||
|
||||
return \strlen($data);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$execCounter = $multi->execCounter;
|
||||
$this->info['pause_handler'] = static function (float $duration) use ($ch, $multi, $execCounter) {
|
||||
if (0 < $duration) {
|
||||
if ($execCounter === $multi->execCounter) {
|
||||
curl_multi_remove_handle($multi->handle, $ch);
|
||||
}
|
||||
|
||||
$lastExpiry = end($multi->pauseExpiries);
|
||||
$multi->pauseExpiries[(int) $ch] = $duration += microtime(true);
|
||||
if (false !== $lastExpiry && $lastExpiry > $duration) {
|
||||
asort($multi->pauseExpiries);
|
||||
}
|
||||
curl_pause($ch, \CURLPAUSE_ALL);
|
||||
} else {
|
||||
unset($multi->pauseExpiries[(int) $ch]);
|
||||
curl_pause($ch, \CURLPAUSE_CONT);
|
||||
curl_multi_add_handle($multi->handle, $ch);
|
||||
}
|
||||
};
|
||||
|
||||
$this->inflate = !isset($options['normalized_headers']['accept-encoding']);
|
||||
curl_pause($ch, \CURLPAUSE_CONT);
|
||||
|
||||
if ($onProgress = $options['on_progress']) {
|
||||
$url = isset($info['url']) ? ['url' => $info['url']] : [];
|
||||
curl_setopt($ch, \CURLOPT_NOPROGRESS, false);
|
||||
curl_setopt($ch, \CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer) {
|
||||
try {
|
||||
rewind($debugBuffer);
|
||||
$debug = ['debug' => stream_get_contents($debugBuffer)];
|
||||
$onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug);
|
||||
} catch (\Throwable $e) {
|
||||
$multi->handlesActivity[(int) $ch][] = null;
|
||||
$multi->handlesActivity[(int) $ch][] = $e;
|
||||
|
||||
return 1; // Abort the request
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int {
|
||||
if ('H' === (curl_getinfo($ch, \CURLINFO_PRIVATE)[0] ?? null)) {
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = new TransportException(sprintf('Unsupported protocol for "%s"', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int {
|
||||
$multi->handlesActivity[$id][] = $data;
|
||||
|
||||
return \strlen($data);
|
||||
});
|
||||
|
||||
$multi->handlesActivity[$id][] = $data;
|
||||
|
||||
return \strlen($data);
|
||||
});
|
||||
|
||||
$this->initializer = static function (self $response) {
|
||||
$waitFor = curl_getinfo($ch = $response->handle, \CURLINFO_PRIVATE);
|
||||
|
||||
return 'H' === $waitFor[0];
|
||||
};
|
||||
|
||||
// Schedule the request in a non-blocking way
|
||||
$multi->lastTimeout = null;
|
||||
$multi->openHandles[$id] = [$ch, $options];
|
||||
curl_multi_add_handle($multi->handle, $ch);
|
||||
|
||||
$this->canary = new Canary(static function () use ($ch, $multi, $id) {
|
||||
unset($multi->pauseExpiries[$id], $multi->openHandles[$id], $multi->handlesActivity[$id]);
|
||||
curl_setopt($ch, \CURLOPT_PRIVATE, '_0');
|
||||
|
||||
if ($multi->performing) {
|
||||
return;
|
||||
}
|
||||
|
||||
curl_multi_remove_handle($multi->handle, $ch);
|
||||
curl_setopt_array($ch, [
|
||||
\CURLOPT_NOPROGRESS => true,
|
||||
\CURLOPT_PROGRESSFUNCTION => null,
|
||||
\CURLOPT_HEADERFUNCTION => null,
|
||||
\CURLOPT_WRITEFUNCTION => null,
|
||||
\CURLOPT_READFUNCTION => null,
|
||||
\CURLOPT_INFILE => null,
|
||||
]);
|
||||
|
||||
if (!$multi->openHandles) {
|
||||
// Schedule DNS cache eviction for the next request
|
||||
$multi->dnsCache->evictions = $multi->dnsCache->evictions ?: $multi->dnsCache->removals;
|
||||
$multi->dnsCache->removals = $multi->dnsCache->hostnames = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
if (!$info = $this->finalInfo) {
|
||||
$info = array_merge($this->info, curl_getinfo($this->handle));
|
||||
$info['url'] = $this->info['url'] ?? $info['url'];
|
||||
$info['redirect_url'] = $this->info['redirect_url'] ?? null;
|
||||
|
||||
// workaround curl not subtracting the time offset for pushed responses
|
||||
if (isset($this->info['url']) && $info['start_time'] / 1000 < $info['total_time']) {
|
||||
$info['total_time'] -= $info['starttransfer_time'] ?: $info['total_time'];
|
||||
$info['starttransfer_time'] = 0.0;
|
||||
}
|
||||
|
||||
rewind($this->debugBuffer);
|
||||
$info['debug'] = stream_get_contents($this->debugBuffer);
|
||||
$waitFor = curl_getinfo($this->handle, \CURLINFO_PRIVATE);
|
||||
|
||||
if ('H' !== $waitFor[0] && 'C' !== $waitFor[0]) {
|
||||
curl_setopt($this->handle, \CURLOPT_VERBOSE, false);
|
||||
rewind($this->debugBuffer);
|
||||
ftruncate($this->debugBuffer, 0);
|
||||
$this->finalInfo = $info;
|
||||
}
|
||||
}
|
||||
|
||||
return null !== $type ? $info[$type] ?? null : $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContent(bool $throw = true): string
|
||||
{
|
||||
$performing = $this->multi->performing;
|
||||
$this->multi->performing = $performing || '_0' === curl_getinfo($this->handle, \CURLINFO_PRIVATE);
|
||||
|
||||
try {
|
||||
return $this->doGetContent($throw);
|
||||
} finally {
|
||||
$this->multi->performing = $performing;
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
try {
|
||||
if (null === $this->timeout) {
|
||||
return; // Unused pushed response
|
||||
}
|
||||
|
||||
$this->doDestruct();
|
||||
} finally {
|
||||
if (\is_resource($this->handle) || $this->handle instanceof \CurlHandle) {
|
||||
curl_setopt($this->handle, \CURLOPT_VERBOSE, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private static function schedule(self $response, array &$runningResponses): void
|
||||
{
|
||||
if (isset($runningResponses[$i = (int) $response->multi->handle])) {
|
||||
$runningResponses[$i][1][$response->id] = $response;
|
||||
} else {
|
||||
$runningResponses[$i] = [$response->multi, [$response->id => $response]];
|
||||
}
|
||||
|
||||
if ('_0' === curl_getinfo($ch = $response->handle, \CURLINFO_PRIVATE)) {
|
||||
// Response already completed
|
||||
$response->multi->handlesActivity[$response->id][] = null;
|
||||
$response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param CurlClientState $multi
|
||||
*/
|
||||
private static function perform(ClientState $multi, ?array &$responses = null): void
|
||||
{
|
||||
if ($multi->performing) {
|
||||
if ($responses) {
|
||||
$response = current($responses);
|
||||
$multi->handlesActivity[(int) $response->handle][] = null;
|
||||
$multi->handlesActivity[(int) $response->handle][] = new TransportException(sprintf('Userland callback cannot use the client nor the response while processing "%s".', curl_getinfo($response->handle, \CURLINFO_EFFECTIVE_URL)));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$multi->performing = true;
|
||||
++$multi->execCounter;
|
||||
$active = 0;
|
||||
while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($multi->handle, $active))) {
|
||||
}
|
||||
|
||||
if (\CURLM_OK !== $err) {
|
||||
throw new TransportException(curl_multi_strerror($err));
|
||||
}
|
||||
|
||||
while ($info = curl_multi_info_read($multi->handle)) {
|
||||
if (\CURLMSG_DONE !== $info['msg']) {
|
||||
continue;
|
||||
}
|
||||
$result = $info['result'];
|
||||
$id = (int) $ch = $info['handle'];
|
||||
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
|
||||
|
||||
if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /* CURLE_HTTP2 */ 16, /* CURLE_HTTP2_STREAM */ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) {
|
||||
curl_multi_remove_handle($multi->handle, $ch);
|
||||
$waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
|
||||
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
|
||||
curl_setopt($ch, \CURLOPT_FORBID_REUSE, true);
|
||||
|
||||
if (0 === curl_multi_add_handle($multi->handle, $ch)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) {
|
||||
$multi->handlesActivity[$id][] = new FirstChunk();
|
||||
}
|
||||
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
|
||||
}
|
||||
} finally {
|
||||
$multi->performing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param CurlClientState $multi
|
||||
*/
|
||||
private static function select(ClientState $multi, float $timeout): int
|
||||
{
|
||||
if (\PHP_VERSION_ID < 70211) {
|
||||
// workaround https://bugs.php.net/76480
|
||||
$timeout = min($timeout, 0.01);
|
||||
}
|
||||
|
||||
if ($multi->pauseExpiries) {
|
||||
$now = microtime(true);
|
||||
|
||||
foreach ($multi->pauseExpiries as $id => $pauseExpiry) {
|
||||
if ($now < $pauseExpiry) {
|
||||
$timeout = min($timeout, $pauseExpiry - $now);
|
||||
break;
|
||||
}
|
||||
|
||||
unset($multi->pauseExpiries[$id]);
|
||||
curl_pause($multi->openHandles[$id][0], \CURLPAUSE_CONT);
|
||||
curl_multi_add_handle($multi->handle, $multi->openHandles[$id][0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 !== $selected = curl_multi_select($multi->handle, $timeout)) {
|
||||
return $selected;
|
||||
}
|
||||
|
||||
if ($multi->pauseExpiries && 0 < $timeout -= microtime(true) - $now) {
|
||||
usleep((int) (1E6 * $timeout));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses header lines as curl yields them to us.
|
||||
*/
|
||||
private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, CurlClientState $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger): int
|
||||
{
|
||||
if (!str_ends_with($data, "\r\n")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
|
||||
|
||||
if ('H' !== $waitFor[0]) {
|
||||
return \strlen($data); // Ignore HTTP trailers
|
||||
}
|
||||
|
||||
$statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE);
|
||||
|
||||
if ($statusCode !== $info['http_code'] && !preg_match("#^HTTP/\d+(?:\.\d+)? {$statusCode}(?: |\r\n$)#", $data)) {
|
||||
return \strlen($data); // Ignore headers from responses to CONNECT requests
|
||||
}
|
||||
|
||||
if ("\r\n" !== $data) {
|
||||
// Regular header line: add it to the list
|
||||
self::addResponseHeaders([substr($data, 0, -2)], $info, $headers);
|
||||
|
||||
if (!str_starts_with($data, 'HTTP/')) {
|
||||
if (0 === stripos($data, 'Location:')) {
|
||||
$location = trim(substr($data, 9, -2));
|
||||
}
|
||||
|
||||
return \strlen($data);
|
||||
}
|
||||
|
||||
if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, \CURLINFO_CERTINFO)) {
|
||||
$info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert'));
|
||||
}
|
||||
|
||||
if (300 <= $info['http_code'] && $info['http_code'] < 400) {
|
||||
if (curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
|
||||
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
|
||||
} elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) {
|
||||
curl_setopt($ch, \CURLOPT_POSTFIELDS, '');
|
||||
}
|
||||
}
|
||||
|
||||
return \strlen($data);
|
||||
}
|
||||
|
||||
// End of headers: handle informational responses, redirects, etc.
|
||||
|
||||
if (200 > $statusCode) {
|
||||
$multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers);
|
||||
$location = null;
|
||||
|
||||
return \strlen($data);
|
||||
}
|
||||
|
||||
$info['redirect_url'] = null;
|
||||
|
||||
if (300 <= $statusCode && $statusCode < 400 && null !== $location) {
|
||||
if ($noContent = 303 === $statusCode || ('POST' === $info['http_method'] && \in_array($statusCode, [301, 302], true))) {
|
||||
$info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET';
|
||||
curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']);
|
||||
}
|
||||
|
||||
if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent)) {
|
||||
$options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT);
|
||||
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
|
||||
curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']);
|
||||
} else {
|
||||
$url = parse_url($location ?? ':');
|
||||
|
||||
if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) {
|
||||
// Populate DNS cache for redirects if needed
|
||||
$port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL), \PHP_URL_SCHEME)) ? 80 : 443);
|
||||
curl_setopt($ch, \CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]);
|
||||
$multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (401 === $statusCode && isset($options['auth_ntlm']) && 0 === strncasecmp($headers['www-authenticate'][0] ?? '', 'NTLM ', 5)) {
|
||||
// Continue with NTLM auth
|
||||
} elseif ($statusCode < 300 || 400 <= $statusCode || null === $location || curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
|
||||
// Headers and redirects completed, time to get the response's content
|
||||
$multi->handlesActivity[$id][] = new FirstChunk();
|
||||
|
||||
if ('HEAD' === $info['http_method'] || \in_array($statusCode, [204, 304], true)) {
|
||||
$waitFor = '_0'; // no content expected
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
} else {
|
||||
$waitFor[0] = 'C'; // C = content
|
||||
}
|
||||
|
||||
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
|
||||
} elseif (null !== $info['redirect_url'] && $logger) {
|
||||
$logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url']));
|
||||
}
|
||||
|
||||
$location = null;
|
||||
|
||||
return \strlen($data);
|
||||
}
|
||||
}
|
80
vendor/symfony/http-client/Response/HttplugPromise.php
vendored
Normal file
80
vendor/symfony/http-client/Response/HttplugPromise.php
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use GuzzleHttp\Promise\Create;
|
||||
use GuzzleHttp\Promise\PromiseInterface as GuzzlePromiseInterface;
|
||||
use Http\Promise\Promise as HttplugPromiseInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class HttplugPromise implements HttplugPromiseInterface
|
||||
{
|
||||
private $promise;
|
||||
|
||||
public function __construct(GuzzlePromiseInterface $promise)
|
||||
{
|
||||
$this->promise = $promise;
|
||||
}
|
||||
|
||||
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): self
|
||||
{
|
||||
return new self($this->promise->then(
|
||||
$this->wrapThenCallback($onFulfilled),
|
||||
$this->wrapThenCallback($onRejected)
|
||||
));
|
||||
}
|
||||
|
||||
public function cancel(): void
|
||||
{
|
||||
$this->promise->cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getState(): string
|
||||
{
|
||||
return $this->promise->getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return Psr7ResponseInterface|mixed
|
||||
*/
|
||||
public function wait($unwrap = true)
|
||||
{
|
||||
$result = $this->promise->wait($unwrap);
|
||||
|
||||
while ($result instanceof HttplugPromiseInterface || $result instanceof GuzzlePromiseInterface) {
|
||||
$result = $result->wait($unwrap);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function wrapThenCallback(?callable $callback): ?callable
|
||||
{
|
||||
if (null === $callback) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return static function ($value) use ($callback) {
|
||||
return Create::promiseFor($callback($value));
|
||||
};
|
||||
}
|
||||
}
|
343
vendor/symfony/http-client/Response/MockResponse.php
vendored
Normal file
343
vendor/symfony/http-client/Response/MockResponse.php
vendored
Normal file
|
@ -0,0 +1,343 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Symfony\Component\HttpClient\Chunk\ErrorChunk;
|
||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A test-friendly response.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class MockResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
use CommonResponseTrait;
|
||||
use TransportResponseTrait {
|
||||
doDestruct as public __destruct;
|
||||
}
|
||||
|
||||
private $body;
|
||||
private $requestOptions = [];
|
||||
private $requestUrl;
|
||||
private $requestMethod;
|
||||
|
||||
private static $mainMulti;
|
||||
private static $idSequence = 0;
|
||||
|
||||
/**
|
||||
* @param string|string[]|iterable $body The response body as a string or an iterable of strings,
|
||||
* yielding an empty string simulates an idle timeout,
|
||||
* throwing an exception yields an ErrorChunk
|
||||
*
|
||||
* @see ResponseInterface::getInfo() for possible info, e.g. "response_headers"
|
||||
*/
|
||||
public function __construct($body = '', array $info = [])
|
||||
{
|
||||
$this->body = is_iterable($body) ? $body : (string) $body;
|
||||
$this->info = $info + ['http_code' => 200] + $this->info;
|
||||
|
||||
if (!isset($info['response_headers'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$responseHeaders = [];
|
||||
|
||||
foreach ($info['response_headers'] as $k => $v) {
|
||||
foreach ((array) $v as $v) {
|
||||
$responseHeaders[] = (\is_string($k) ? $k.': ' : '').$v;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info['response_headers'] = [];
|
||||
self::addResponseHeaders($responseHeaders, $this->info, $this->headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the options used when doing the request.
|
||||
*/
|
||||
public function getRequestOptions(): array
|
||||
{
|
||||
return $this->requestOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL used when doing the request.
|
||||
*/
|
||||
public function getRequestUrl(): string
|
||||
{
|
||||
return $this->requestUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method used when doing the request.
|
||||
*/
|
||||
public function getRequestMethod(): string
|
||||
{
|
||||
return $this->requestMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
return null !== $type ? $this->info[$type] ?? null : $this->info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cancel(): void
|
||||
{
|
||||
$this->info['canceled'] = true;
|
||||
$this->info['error'] = 'Response has been canceled.';
|
||||
try {
|
||||
$this->body = null;
|
||||
} catch (TransportException $e) {
|
||||
// ignore errors when canceling
|
||||
}
|
||||
|
||||
$onProgress = $this->requestOptions['on_progress'] ?? static function () {};
|
||||
$dlSize = isset($this->headers['content-encoding']) || 'HEAD' === ($this->info['http_method'] ?? null) || \in_array($this->info['http_code'], [204, 304], true) ? 0 : (int) ($this->headers['content-length'][0] ?? 0);
|
||||
$onProgress($this->offset, $dlSize, $this->info);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function close(): void
|
||||
{
|
||||
$this->inflate = null;
|
||||
$this->body = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function fromRequest(string $method, string $url, array $options, ResponseInterface $mock): self
|
||||
{
|
||||
$response = new self([]);
|
||||
$response->requestOptions = $options;
|
||||
$response->id = ++self::$idSequence;
|
||||
$response->shouldBuffer = $options['buffer'] ?? true;
|
||||
$response->initializer = static function (self $response) {
|
||||
return \is_array($response->body[0] ?? null);
|
||||
};
|
||||
|
||||
$response->info['redirect_count'] = 0;
|
||||
$response->info['redirect_url'] = null;
|
||||
$response->info['start_time'] = microtime(true);
|
||||
$response->info['http_method'] = $method;
|
||||
$response->info['http_code'] = 0;
|
||||
$response->info['user_data'] = $options['user_data'] ?? null;
|
||||
$response->info['max_duration'] = $options['max_duration'] ?? null;
|
||||
$response->info['url'] = $url;
|
||||
|
||||
if ($mock instanceof self) {
|
||||
$mock->requestOptions = $response->requestOptions;
|
||||
$mock->requestMethod = $method;
|
||||
$mock->requestUrl = $url;
|
||||
}
|
||||
|
||||
self::writeRequest($response, $options, $mock);
|
||||
$response->body[] = [$options, $mock];
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function schedule(self $response, array &$runningResponses): void
|
||||
{
|
||||
if (!$response->id) {
|
||||
throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.');
|
||||
}
|
||||
|
||||
$multi = self::$mainMulti ?? self::$mainMulti = new ClientState();
|
||||
|
||||
if (!isset($runningResponses[0])) {
|
||||
$runningResponses[0] = [$multi, []];
|
||||
}
|
||||
|
||||
$runningResponses[0][1][$response->id] = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function perform(ClientState $multi, array &$responses): void
|
||||
{
|
||||
foreach ($responses as $response) {
|
||||
$id = $response->id;
|
||||
|
||||
if (null === $response->body) {
|
||||
// Canceled response
|
||||
$response->body = [];
|
||||
} elseif ([] === $response->body) {
|
||||
// Error chunk
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null;
|
||||
} elseif (null === $chunk = array_shift($response->body)) {
|
||||
// Last chunk
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = array_shift($response->body);
|
||||
} elseif (\is_array($chunk)) {
|
||||
// First chunk
|
||||
try {
|
||||
$offset = 0;
|
||||
$chunk[1]->getStatusCode();
|
||||
$chunk[1]->getHeaders(false);
|
||||
self::readResponse($response, $chunk[0], $chunk[1], $offset);
|
||||
$multi->handlesActivity[$id][] = new FirstChunk();
|
||||
$buffer = $response->requestOptions['buffer'] ?? null;
|
||||
|
||||
if ($buffer instanceof \Closure && $response->content = $buffer($response->headers) ?: null) {
|
||||
$response->content = \is_resource($response->content) ? $response->content : fopen('php://temp', 'w+');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = $e;
|
||||
}
|
||||
} elseif ($chunk instanceof \Throwable) {
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = $chunk;
|
||||
} else {
|
||||
// Data or timeout chunk
|
||||
$multi->handlesActivity[$id][] = $chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function select(ClientState $multi, float $timeout): int
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates sending the request.
|
||||
*/
|
||||
private static function writeRequest(self $response, array $options, ResponseInterface $mock)
|
||||
{
|
||||
$onProgress = $options['on_progress'] ?? static function () {};
|
||||
$response->info += $mock->getInfo() ?: [];
|
||||
|
||||
// simulate "size_upload" if it is set
|
||||
if (isset($response->info['size_upload'])) {
|
||||
$response->info['size_upload'] = 0.0;
|
||||
}
|
||||
|
||||
// simulate "total_time" if it is not set
|
||||
if (!isset($response->info['total_time'])) {
|
||||
$response->info['total_time'] = microtime(true) - $response->info['start_time'];
|
||||
}
|
||||
|
||||
// "notify" DNS resolution
|
||||
$onProgress(0, 0, $response->info);
|
||||
|
||||
// consume the request body
|
||||
if (\is_resource($body = $options['body'] ?? '')) {
|
||||
$data = stream_get_contents($body);
|
||||
if (isset($response->info['size_upload'])) {
|
||||
$response->info['size_upload'] += \strlen($data);
|
||||
}
|
||||
} elseif ($body instanceof \Closure) {
|
||||
while ('' !== $data = $body(16372)) {
|
||||
if (!\is_string($data)) {
|
||||
throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data)));
|
||||
}
|
||||
|
||||
// "notify" upload progress
|
||||
if (isset($response->info['size_upload'])) {
|
||||
$response->info['size_upload'] += \strlen($data);
|
||||
}
|
||||
|
||||
$onProgress(0, 0, $response->info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates reading the response.
|
||||
*/
|
||||
private static function readResponse(self $response, array $options, ResponseInterface $mock, int &$offset)
|
||||
{
|
||||
$onProgress = $options['on_progress'] ?? static function () {};
|
||||
|
||||
// populate info related to headers
|
||||
$info = $mock->getInfo() ?: [];
|
||||
$response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200;
|
||||
$response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers);
|
||||
$dlSize = isset($response->headers['content-encoding']) || 'HEAD' === $response->info['http_method'] || \in_array($response->info['http_code'], [204, 304], true) ? 0 : (int) ($response->headers['content-length'][0] ?? 0);
|
||||
|
||||
$response->info = [
|
||||
'start_time' => $response->info['start_time'],
|
||||
'user_data' => $response->info['user_data'],
|
||||
'max_duration' => $response->info['max_duration'],
|
||||
'http_code' => $response->info['http_code'],
|
||||
] + $info + $response->info;
|
||||
|
||||
if (null !== $response->info['error']) {
|
||||
throw new TransportException($response->info['error']);
|
||||
}
|
||||
|
||||
if (!isset($response->info['total_time'])) {
|
||||
$response->info['total_time'] = microtime(true) - $response->info['start_time'];
|
||||
}
|
||||
|
||||
// "notify" headers arrival
|
||||
$onProgress(0, $dlSize, $response->info);
|
||||
|
||||
// cast response body to activity list
|
||||
$body = $mock instanceof self ? $mock->body : $mock->getContent(false);
|
||||
|
||||
if (!\is_string($body)) {
|
||||
try {
|
||||
foreach ($body as $chunk) {
|
||||
if ('' === $chunk = (string) $chunk) {
|
||||
// simulate an idle timeout
|
||||
$response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url']));
|
||||
} else {
|
||||
$response->body[] = $chunk;
|
||||
$offset += \strlen($chunk);
|
||||
// "notify" download progress
|
||||
$onProgress($offset, $dlSize, $response->info);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$response->body[] = $e;
|
||||
}
|
||||
} elseif ('' !== $body) {
|
||||
$response->body[] = $body;
|
||||
$offset = \strlen($body);
|
||||
}
|
||||
|
||||
if (!isset($response->info['total_time'])) {
|
||||
$response->info['total_time'] = microtime(true) - $response->info['start_time'];
|
||||
}
|
||||
|
||||
// "notify" completion
|
||||
$onProgress($offset, $dlSize, $response->info);
|
||||
|
||||
if ($dlSize && $offset !== $dlSize) {
|
||||
throw new TransportException(sprintf('Transfer closed with %d bytes remaining to read.', $dlSize - $offset));
|
||||
}
|
||||
}
|
||||
}
|
376
vendor/symfony/http-client/Response/NativeResponse.php
vendored
Normal file
376
vendor/symfony/http-client/Response/NativeResponse.php
vendored
Normal file
|
@ -0,0 +1,376 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Internal\Canary;
|
||||
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||
use Symfony\Component\HttpClient\Internal\NativeClientState;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class NativeResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
use CommonResponseTrait;
|
||||
use TransportResponseTrait;
|
||||
|
||||
private $context;
|
||||
private $url;
|
||||
private $resolver;
|
||||
private $onProgress;
|
||||
private $remaining;
|
||||
private $buffer;
|
||||
private $multi;
|
||||
private $pauseExpiry = 0;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(NativeClientState $multi, $context, string $url, array $options, array &$info, callable $resolver, ?callable $onProgress, ?LoggerInterface $logger)
|
||||
{
|
||||
$this->multi = $multi;
|
||||
$this->id = $id = (int) $context;
|
||||
$this->context = $context;
|
||||
$this->url = $url;
|
||||
$this->logger = $logger;
|
||||
$this->timeout = $options['timeout'];
|
||||
$this->info = &$info;
|
||||
$this->resolver = $resolver;
|
||||
$this->onProgress = $onProgress;
|
||||
$this->inflate = !isset($options['normalized_headers']['accept-encoding']);
|
||||
$this->shouldBuffer = $options['buffer'] ?? true;
|
||||
|
||||
// Temporary resource to dechunk the response stream
|
||||
$this->buffer = fopen('php://temp', 'w+');
|
||||
|
||||
$info['user_data'] = $options['user_data'];
|
||||
$info['max_duration'] = $options['max_duration'];
|
||||
++$multi->responseCount;
|
||||
|
||||
$this->initializer = static function (self $response) {
|
||||
return null === $response->remaining;
|
||||
};
|
||||
|
||||
$pauseExpiry = &$this->pauseExpiry;
|
||||
$info['pause_handler'] = static function (float $duration) use (&$pauseExpiry) {
|
||||
$pauseExpiry = 0 < $duration ? microtime(true) + $duration : 0;
|
||||
};
|
||||
|
||||
$this->canary = new Canary(static function () use ($multi, $id) {
|
||||
if (null !== ($host = $multi->openHandles[$id][6] ?? null) && 0 >= --$multi->hosts[$host]) {
|
||||
unset($multi->hosts[$host]);
|
||||
}
|
||||
unset($multi->openHandles[$id], $multi->handlesActivity[$id]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
if (!$info = $this->finalInfo) {
|
||||
$info = $this->info;
|
||||
$info['url'] = implode('', $info['url']);
|
||||
unset($info['size_body'], $info['request_header']);
|
||||
|
||||
if (null === $this->buffer) {
|
||||
$this->finalInfo = $info;
|
||||
}
|
||||
}
|
||||
|
||||
return null !== $type ? $info[$type] ?? null : $info;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
try {
|
||||
$this->doDestruct();
|
||||
} finally {
|
||||
// Clear the DNS cache when all requests completed
|
||||
if (0 >= --$this->multi->responseCount) {
|
||||
$this->multi->responseCount = 0;
|
||||
$this->multi->dnsCache = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function open(): void
|
||||
{
|
||||
$url = $this->url;
|
||||
|
||||
set_error_handler(function ($type, $msg) use (&$url) {
|
||||
if (\E_NOTICE !== $type || 'fopen(): Content-type not specified assuming application/x-www-form-urlencoded' !== $msg) {
|
||||
throw new TransportException($msg);
|
||||
}
|
||||
|
||||
$this->logger && $this->logger->info(sprintf('%s for "%s".', $msg, $url ?? $this->url));
|
||||
});
|
||||
|
||||
try {
|
||||
$this->info['start_time'] = microtime(true);
|
||||
|
||||
[$resolver, $url] = ($this->resolver)($this->multi);
|
||||
|
||||
while (true) {
|
||||
$context = stream_context_get_options($this->context);
|
||||
|
||||
if ($proxy = $context['http']['proxy'] ?? null) {
|
||||
$this->info['debug'] .= "* Establish HTTP proxy tunnel to {$proxy}\n";
|
||||
$this->info['request_header'] = $url;
|
||||
} else {
|
||||
$this->info['debug'] .= "* Trying {$this->info['primary_ip']}...\n";
|
||||
$this->info['request_header'] = $this->info['url']['path'].$this->info['url']['query'];
|
||||
}
|
||||
|
||||
$this->info['request_header'] = sprintf("> %s %s HTTP/%s \r\n", $context['http']['method'], $this->info['request_header'], $context['http']['protocol_version']);
|
||||
$this->info['request_header'] .= implode("\r\n", $context['http']['header'])."\r\n\r\n";
|
||||
|
||||
if (\array_key_exists('peer_name', $context['ssl']) && null === $context['ssl']['peer_name']) {
|
||||
unset($context['ssl']['peer_name']);
|
||||
$this->context = stream_context_create([], ['options' => $context] + stream_context_get_params($this->context));
|
||||
}
|
||||
|
||||
// Send request and follow redirects when needed
|
||||
$this->handle = $h = fopen($url, 'r', false, $this->context);
|
||||
self::addResponseHeaders(stream_get_meta_data($h)['wrapper_data'], $this->info, $this->headers, $this->info['debug']);
|
||||
$url = $resolver($this->multi, $this->headers['location'][0] ?? null, $this->context);
|
||||
|
||||
if (null === $url) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->logger && $this->logger->info(sprintf('Redirecting: "%s %s"', $this->info['http_code'], $url ?? $this->url));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->close();
|
||||
$this->multi->handlesActivity[$this->id][] = null;
|
||||
$this->multi->handlesActivity[$this->id][] = $e;
|
||||
|
||||
return;
|
||||
} finally {
|
||||
$this->info['pretransfer_time'] = $this->info['total_time'] = microtime(true) - $this->info['start_time'];
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
if (isset($context['ssl']['capture_peer_cert_chain']) && isset(($context = stream_context_get_options($this->context))['ssl']['peer_certificate_chain'])) {
|
||||
$this->info['peer_certificate_chain'] = $context['ssl']['peer_certificate_chain'];
|
||||
}
|
||||
|
||||
stream_set_blocking($h, false);
|
||||
$this->context = $this->resolver = null;
|
||||
|
||||
// Create dechunk buffers
|
||||
if (isset($this->headers['content-length'])) {
|
||||
$this->remaining = (int) $this->headers['content-length'][0];
|
||||
} elseif ('chunked' === ($this->headers['transfer-encoding'][0] ?? null)) {
|
||||
stream_filter_append($this->buffer, 'dechunk', \STREAM_FILTER_WRITE);
|
||||
$this->remaining = -1;
|
||||
} else {
|
||||
$this->remaining = -2;
|
||||
}
|
||||
|
||||
$this->multi->handlesActivity[$this->id] = [new FirstChunk()];
|
||||
|
||||
if ('HEAD' === $context['http']['method'] || \in_array($this->info['http_code'], [204, 304], true)) {
|
||||
$this->multi->handlesActivity[$this->id][] = null;
|
||||
$this->multi->handlesActivity[$this->id][] = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$host = parse_url($this->info['redirect_url'] ?? $this->url, \PHP_URL_HOST);
|
||||
$this->multi->lastTimeout = null;
|
||||
$this->multi->openHandles[$this->id] = [&$this->pauseExpiry, $h, $this->buffer, $this->onProgress, &$this->remaining, &$this->info, $host];
|
||||
$this->multi->hosts[$host] = 1 + ($this->multi->hosts[$host] ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private function close(): void
|
||||
{
|
||||
$this->canary->cancel();
|
||||
$this->handle = $this->buffer = $this->inflate = $this->onProgress = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private static function schedule(self $response, array &$runningResponses): void
|
||||
{
|
||||
if (!isset($runningResponses[$i = $response->multi->id])) {
|
||||
$runningResponses[$i] = [$response->multi, []];
|
||||
}
|
||||
|
||||
$runningResponses[$i][1][$response->id] = $response;
|
||||
|
||||
if (null === $response->buffer) {
|
||||
// Response already completed
|
||||
$response->multi->handlesActivity[$response->id][] = null;
|
||||
$response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param NativeClientState $multi
|
||||
*/
|
||||
private static function perform(ClientState $multi, ?array &$responses = null): void
|
||||
{
|
||||
foreach ($multi->openHandles as $i => [$pauseExpiry, $h, $buffer, $onProgress]) {
|
||||
if ($pauseExpiry) {
|
||||
if (microtime(true) < $pauseExpiry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$multi->openHandles[$i][0] = 0;
|
||||
}
|
||||
|
||||
$hasActivity = false;
|
||||
$remaining = &$multi->openHandles[$i][4];
|
||||
$info = &$multi->openHandles[$i][5];
|
||||
$e = null;
|
||||
|
||||
// Read incoming buffer and write it to the dechunk one
|
||||
try {
|
||||
if ($remaining && '' !== $data = (string) fread($h, 0 > $remaining ? 16372 : $remaining)) {
|
||||
fwrite($buffer, $data);
|
||||
$hasActivity = true;
|
||||
$multi->sleep = false;
|
||||
|
||||
if (-1 !== $remaining) {
|
||||
$remaining -= \strlen($data);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$hasActivity = $onProgress = false;
|
||||
}
|
||||
|
||||
if (!$hasActivity) {
|
||||
if ($onProgress) {
|
||||
try {
|
||||
// Notify the progress callback so that it can e.g. cancel
|
||||
// the request if the stream is inactive for too long
|
||||
$info['total_time'] = microtime(true) - $info['start_time'];
|
||||
$onProgress();
|
||||
} catch (\Throwable $e) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
} elseif ('' !== $data = stream_get_contents($buffer, -1, 0)) {
|
||||
rewind($buffer);
|
||||
ftruncate($buffer, 0);
|
||||
|
||||
if (null === $e) {
|
||||
$multi->handlesActivity[$i][] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $e || !$remaining || feof($h)) {
|
||||
// Stream completed
|
||||
$info['total_time'] = microtime(true) - $info['start_time'];
|
||||
$info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time'];
|
||||
|
||||
if ($onProgress) {
|
||||
try {
|
||||
$onProgress(-1);
|
||||
} catch (\Throwable $e) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $e) {
|
||||
if (0 < $remaining) {
|
||||
$e = new TransportException(sprintf('Transfer closed with %s bytes remaining to read.', $remaining));
|
||||
} elseif (-1 === $remaining && fwrite($buffer, '-') && '' !== stream_get_contents($buffer, -1, 0)) {
|
||||
$e = new TransportException('Transfer closed with outstanding data remaining from chunked response.');
|
||||
}
|
||||
}
|
||||
|
||||
$multi->handlesActivity[$i][] = null;
|
||||
$multi->handlesActivity[$i][] = $e;
|
||||
if (null !== ($host = $multi->openHandles[$i][6] ?? null) && 0 >= --$multi->hosts[$host]) {
|
||||
unset($multi->hosts[$host]);
|
||||
}
|
||||
unset($multi->openHandles[$i]);
|
||||
$multi->sleep = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $responses) {
|
||||
return;
|
||||
}
|
||||
|
||||
$maxHosts = $multi->maxHostConnections;
|
||||
|
||||
foreach ($responses as $i => $response) {
|
||||
if (null !== $response->remaining || null === $response->buffer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($response->pauseExpiry && microtime(true) < $response->pauseExpiry) {
|
||||
// Create empty open handles to tell we still have pending requests
|
||||
$multi->openHandles[$i] = [\INF, null, null, null];
|
||||
} elseif ($maxHosts && $maxHosts > ($multi->hosts[parse_url($response->url, \PHP_URL_HOST)] ?? 0)) {
|
||||
// Open the next pending request - this is a blocking operation so we do only one of them
|
||||
$response->open();
|
||||
$multi->sleep = false;
|
||||
self::perform($multi);
|
||||
$maxHosts = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param NativeClientState $multi
|
||||
*/
|
||||
private static function select(ClientState $multi, float $timeout): int
|
||||
{
|
||||
if (!$multi->sleep = !$multi->sleep) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$_ = $handles = [];
|
||||
$now = null;
|
||||
|
||||
foreach ($multi->openHandles as [$pauseExpiry, $h]) {
|
||||
if (null === $h) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($pauseExpiry && ($now ?? $now = microtime(true)) < $pauseExpiry) {
|
||||
$timeout = min($timeout, $pauseExpiry - $now);
|
||||
continue;
|
||||
}
|
||||
|
||||
$handles[] = $h;
|
||||
}
|
||||
|
||||
if (!$handles) {
|
||||
usleep((int) (1E6 * $timeout));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return stream_select($handles, $_, $_, (int) $timeout, (int) (1E6 * ($timeout - (int) $timeout)));
|
||||
}
|
||||
}
|
54
vendor/symfony/http-client/Response/ResponseStream.php
vendored
Normal file
54
vendor/symfony/http-client/Response/ResponseStream.php
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class ResponseStream implements ResponseStreamInterface
|
||||
{
|
||||
private $generator;
|
||||
|
||||
public function __construct(\Generator $generator)
|
||||
{
|
||||
$this->generator = $generator;
|
||||
}
|
||||
|
||||
public function key(): ResponseInterface
|
||||
{
|
||||
return $this->generator->key();
|
||||
}
|
||||
|
||||
public function current(): ChunkInterface
|
||||
{
|
||||
return $this->generator->current();
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
$this->generator->next();
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->generator->rewind();
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return $this->generator->valid();
|
||||
}
|
||||
}
|
313
vendor/symfony/http-client/Response/StreamWrapper.php
vendored
Normal file
313
vendor/symfony/http-client/Response/StreamWrapper.php
vendored
Normal file
|
@ -0,0 +1,313 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Allows turning ResponseInterface instances to PHP streams.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class StreamWrapper
|
||||
{
|
||||
/** @var resource|null */
|
||||
public $context;
|
||||
|
||||
/** @var HttpClientInterface */
|
||||
private $client;
|
||||
|
||||
/** @var ResponseInterface */
|
||||
private $response;
|
||||
|
||||
/** @var resource|string|null */
|
||||
private $content;
|
||||
|
||||
/** @var resource|null */
|
||||
private $handle;
|
||||
|
||||
private $blocking = true;
|
||||
private $timeout;
|
||||
private $eof = false;
|
||||
private $offset = 0;
|
||||
|
||||
/**
|
||||
* Creates a PHP stream resource from a ResponseInterface.
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public static function createResource(ResponseInterface $response, ?HttpClientInterface $client = null)
|
||||
{
|
||||
if ($response instanceof StreamableInterface) {
|
||||
$stack = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
|
||||
if ($response !== ($stack[1]['object'] ?? null)) {
|
||||
return $response->toStream(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $client && !method_exists($response, 'stream')) {
|
||||
throw new \InvalidArgumentException(sprintf('Providing a client to "%s()" is required when the response doesn\'t have any "stream()" method.', __CLASS__));
|
||||
}
|
||||
|
||||
static $registered = false;
|
||||
|
||||
if (!$registered = $registered || stream_wrapper_register(strtr(__CLASS__, '\\', '-'), __CLASS__)) {
|
||||
throw new \RuntimeException(error_get_last()['message'] ?? 'Registering the "symfony" stream wrapper failed.');
|
||||
}
|
||||
|
||||
$context = [
|
||||
'client' => $client ?? $response,
|
||||
'response' => $response,
|
||||
];
|
||||
|
||||
return fopen(strtr(__CLASS__, '\\', '-').'://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context]));
|
||||
}
|
||||
|
||||
public function getResponse(): ResponseInterface
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource|callable|null $handle The resource handle that should be monitored when
|
||||
* stream_select() is used on the created stream
|
||||
* @param resource|null $content The seekable resource where the response body is buffered
|
||||
*/
|
||||
public function bindHandles(&$handle, &$content): void
|
||||
{
|
||||
$this->handle = &$handle;
|
||||
$this->content = &$content;
|
||||
$this->offset = null;
|
||||
}
|
||||
|
||||
public function stream_open(string $path, string $mode, int $options): bool
|
||||
{
|
||||
if ('r' !== $mode) {
|
||||
if ($options & \STREAM_REPORT_ERRORS) {
|
||||
trigger_error(sprintf('Invalid mode "%s": only "r" is supported.', $mode), \E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$context = stream_context_get_options($this->context)['symfony'] ?? null;
|
||||
$this->client = $context['client'] ?? null;
|
||||
$this->response = $context['response'] ?? null;
|
||||
$this->context = null;
|
||||
|
||||
if (null !== $this->client && null !== $this->response) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($options & \STREAM_REPORT_ERRORS) {
|
||||
trigger_error('Missing options "client" or "response" in "symfony" stream context.', \E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_read(int $count)
|
||||
{
|
||||
if (\is_resource($this->content)) {
|
||||
// Empty the internal activity list
|
||||
foreach ($this->client->stream([$this->response], 0) as $chunk) {
|
||||
try {
|
||||
if (!$chunk->isTimeout() && $chunk->isFirst()) {
|
||||
$this->response->getStatusCode(); // ignore 3/4/5xx
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
trigger_error($e->getMessage(), \E_USER_WARNING);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 !== fseek($this->content, $this->offset ?? 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ('' !== $data = fread($this->content, $count)) {
|
||||
fseek($this->content, 0, \SEEK_END);
|
||||
$this->offset += \strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_string($this->content)) {
|
||||
if (\strlen($this->content) <= $count) {
|
||||
$data = $this->content;
|
||||
$this->content = null;
|
||||
} else {
|
||||
$data = substr($this->content, 0, $count);
|
||||
$this->content = substr($this->content, $count);
|
||||
}
|
||||
$this->offset += \strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($this->client->stream([$this->response], $this->blocking ? $this->timeout : 0) as $chunk) {
|
||||
try {
|
||||
$this->eof = true;
|
||||
$this->eof = !$chunk->isTimeout();
|
||||
|
||||
if (!$this->eof && !$this->blocking) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->eof = $chunk->isLast();
|
||||
|
||||
if ($chunk->isFirst()) {
|
||||
$this->response->getStatusCode(); // ignore 3/4/5xx
|
||||
}
|
||||
|
||||
if ('' !== $data = $chunk->getContent()) {
|
||||
if (\strlen($data) > $count) {
|
||||
if (null === $this->content) {
|
||||
$this->content = substr($data, $count);
|
||||
}
|
||||
$data = substr($data, 0, $count);
|
||||
}
|
||||
$this->offset += \strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
trigger_error($e->getMessage(), \E_USER_WARNING);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function stream_set_option(int $option, int $arg1, ?int $arg2): bool
|
||||
{
|
||||
if (\STREAM_OPTION_BLOCKING === $option) {
|
||||
$this->blocking = (bool) $arg1;
|
||||
} elseif (\STREAM_OPTION_READ_TIMEOUT === $option) {
|
||||
$this->timeout = $arg1 + $arg2 / 1e6;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stream_tell(): int
|
||||
{
|
||||
return $this->offset ?? 0;
|
||||
}
|
||||
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
return $this->eof && !\is_string($this->content);
|
||||
}
|
||||
|
||||
public function stream_seek(int $offset, int $whence = \SEEK_SET): bool
|
||||
{
|
||||
if (null === $this->content && null === $this->offset) {
|
||||
$this->response->getStatusCode();
|
||||
$this->offset = 0;
|
||||
}
|
||||
|
||||
if (!\is_resource($this->content) || 0 !== fseek($this->content, 0, \SEEK_END)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$size = ftell($this->content);
|
||||
|
||||
if (\SEEK_CUR === $whence) {
|
||||
$offset += $this->offset ?? 0;
|
||||
}
|
||||
|
||||
if (\SEEK_END === $whence || $size < $offset) {
|
||||
foreach ($this->client->stream([$this->response]) as $chunk) {
|
||||
try {
|
||||
if ($chunk->isFirst()) {
|
||||
$this->response->getStatusCode(); // ignore 3/4/5xx
|
||||
}
|
||||
|
||||
// Chunks are buffered in $this->content already
|
||||
$size += \strlen($chunk->getContent());
|
||||
|
||||
if (\SEEK_END !== $whence && $offset <= $size) {
|
||||
break;
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
trigger_error($e->getMessage(), \E_USER_WARNING);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (\SEEK_END === $whence) {
|
||||
$offset += $size;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 <= $offset && $offset <= $size) {
|
||||
$this->eof = false;
|
||||
$this->offset = $offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_cast(int $castAs)
|
||||
{
|
||||
if (\STREAM_CAST_FOR_SELECT === $castAs) {
|
||||
$this->response->getHeaders(false);
|
||||
|
||||
return (\is_callable($this->handle) ? ($this->handle)() : $this->handle) ?? false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_stat(): array
|
||||
{
|
||||
try {
|
||||
$headers = $this->response->getHeaders(false);
|
||||
} catch (ExceptionInterface $e) {
|
||||
trigger_error($e->getMessage(), \E_USER_WARNING);
|
||||
$headers = [];
|
||||
}
|
||||
|
||||
return [
|
||||
'dev' => 0,
|
||||
'ino' => 0,
|
||||
'mode' => 33060,
|
||||
'nlink' => 0,
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'rdev' => 0,
|
||||
'size' => (int) ($headers['content-length'][0] ?? -1),
|
||||
'atime' => 0,
|
||||
'mtime' => strtotime($headers['last-modified'][0] ?? '') ?: 0,
|
||||
'ctime' => 0,
|
||||
'blksize' => 0,
|
||||
'blocks' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
35
vendor/symfony/http-client/Response/StreamableInterface.php
vendored
Normal file
35
vendor/symfony/http-client/Response/StreamableInterface.php
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface StreamableInterface
|
||||
{
|
||||
/**
|
||||
* Casts the response to a PHP stream resource.
|
||||
*
|
||||
* @return resource
|
||||
*
|
||||
* @throws TransportExceptionInterface When a network error occurs
|
||||
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
|
||||
* @throws ClientExceptionInterface On a 4xx when $throw is true
|
||||
* @throws ServerExceptionInterface On a 5xx when $throw is true
|
||||
*/
|
||||
public function toStream(bool $throw = true);
|
||||
}
|
221
vendor/symfony/http-client/Response/TraceableResponse.php
vendored
Normal file
221
vendor/symfony/http-client/Response/TraceableResponse.php
vendored
Normal file
|
@ -0,0 +1,221 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Symfony\Component\HttpClient\Chunk\ErrorChunk;
|
||||
use Symfony\Component\HttpClient\Exception\ClientException;
|
||||
use Symfony\Component\HttpClient\Exception\RedirectionException;
|
||||
use Symfony\Component\HttpClient\Exception\ServerException;
|
||||
use Symfony\Component\HttpClient\TraceableHttpClient;
|
||||
use Symfony\Component\Stopwatch\StopwatchEvent;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TraceableResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
private $client;
|
||||
private $response;
|
||||
private $content;
|
||||
private $event;
|
||||
|
||||
public function __construct(HttpClientInterface $client, ResponseInterface $response, &$content, ?StopwatchEvent $event = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->response = $response;
|
||||
$this->content = &$content;
|
||||
$this->event = $event;
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
try {
|
||||
if (method_exists($this->response, '__destruct')) {
|
||||
$this->response->__destruct();
|
||||
}
|
||||
} finally {
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
try {
|
||||
return $this->response->getStatusCode();
|
||||
} finally {
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->lap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getHeaders(bool $throw = true): array
|
||||
{
|
||||
try {
|
||||
return $this->response->getHeaders($throw);
|
||||
} finally {
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->lap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getContent(bool $throw = true): string
|
||||
{
|
||||
try {
|
||||
if (false === $this->content) {
|
||||
return $this->response->getContent($throw);
|
||||
}
|
||||
|
||||
return $this->content = $this->response->getContent(false);
|
||||
} finally {
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->stop();
|
||||
}
|
||||
if ($throw) {
|
||||
$this->checkStatusCode($this->response->getStatusCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(bool $throw = true): array
|
||||
{
|
||||
try {
|
||||
if (false === $this->content) {
|
||||
return $this->response->toArray($throw);
|
||||
}
|
||||
|
||||
return $this->content = $this->response->toArray(false);
|
||||
} finally {
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->stop();
|
||||
}
|
||||
if ($throw) {
|
||||
$this->checkStatusCode($this->response->getStatusCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function cancel(): void
|
||||
{
|
||||
$this->response->cancel();
|
||||
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->stop();
|
||||
}
|
||||
}
|
||||
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
return $this->response->getInfo($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts the response to a PHP stream resource.
|
||||
*
|
||||
* @return resource
|
||||
*
|
||||
* @throws TransportExceptionInterface When a network error occurs
|
||||
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
|
||||
* @throws ClientExceptionInterface On a 4xx when $throw is true
|
||||
* @throws ServerExceptionInterface On a 5xx when $throw is true
|
||||
*/
|
||||
public function toStream(bool $throw = true)
|
||||
{
|
||||
if ($throw) {
|
||||
// Ensure headers arrived
|
||||
$this->response->getHeaders(true);
|
||||
}
|
||||
|
||||
if ($this->response instanceof StreamableInterface) {
|
||||
return $this->response->toStream(false);
|
||||
}
|
||||
|
||||
return StreamWrapper::createResource($this->response, $this->client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function stream(HttpClientInterface $client, iterable $responses, ?float $timeout): \Generator
|
||||
{
|
||||
$wrappedResponses = [];
|
||||
$traceableMap = new \SplObjectStorage();
|
||||
|
||||
foreach ($responses as $r) {
|
||||
if (!$r instanceof self) {
|
||||
throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', TraceableHttpClient::class, get_debug_type($r)));
|
||||
}
|
||||
|
||||
$traceableMap[$r->response] = $r;
|
||||
$wrappedResponses[] = $r->response;
|
||||
if ($r->event && !$r->event->isStarted()) {
|
||||
$r->event->start();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($client->stream($wrappedResponses, $timeout) as $r => $chunk) {
|
||||
if ($traceableMap[$r]->event && $traceableMap[$r]->event->isStarted()) {
|
||||
try {
|
||||
if ($chunk->isTimeout() || !$chunk->isLast()) {
|
||||
$traceableMap[$r]->event->lap();
|
||||
} else {
|
||||
$traceableMap[$r]->event->stop();
|
||||
}
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$traceableMap[$r]->event->stop();
|
||||
if ($chunk instanceof ErrorChunk) {
|
||||
$chunk->didThrow(false);
|
||||
} else {
|
||||
$chunk = new ErrorChunk($chunk->getOffset(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
yield $traceableMap[$r] => $chunk;
|
||||
}
|
||||
}
|
||||
|
||||
private function checkStatusCode(int $code)
|
||||
{
|
||||
if (500 <= $code) {
|
||||
throw new ServerException($this);
|
||||
}
|
||||
|
||||
if (400 <= $code) {
|
||||
throw new ClientException($this);
|
||||
}
|
||||
|
||||
if (300 <= $code) {
|
||||
throw new RedirectionException($this);
|
||||
}
|
||||
}
|
||||
}
|
312
vendor/symfony/http-client/Response/TransportResponseTrait.php
vendored
Normal file
312
vendor/symfony/http-client/Response/TransportResponseTrait.php
vendored
Normal file
|
@ -0,0 +1,312 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Symfony\Component\HttpClient\Chunk\DataChunk;
|
||||
use Symfony\Component\HttpClient\Chunk\ErrorChunk;
|
||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use Symfony\Component\HttpClient\Chunk\LastChunk;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||
|
||||
/**
|
||||
* Implements common logic for transport-level response classes.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait TransportResponseTrait
|
||||
{
|
||||
private $canary;
|
||||
private $headers = [];
|
||||
private $info = [
|
||||
'response_headers' => [],
|
||||
'http_code' => 0,
|
||||
'error' => null,
|
||||
'canceled' => false,
|
||||
];
|
||||
|
||||
/** @var object|resource */
|
||||
private $handle;
|
||||
private $id;
|
||||
private $timeout = 0;
|
||||
private $inflate;
|
||||
private $finalInfo;
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
if ($this->initializer) {
|
||||
self::initialize($this);
|
||||
}
|
||||
|
||||
return $this->info['http_code'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHeaders(bool $throw = true): array
|
||||
{
|
||||
if ($this->initializer) {
|
||||
self::initialize($this);
|
||||
}
|
||||
|
||||
if ($throw) {
|
||||
$this->checkStatusCode();
|
||||
}
|
||||
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cancel(): void
|
||||
{
|
||||
$this->info['canceled'] = true;
|
||||
$this->info['error'] = 'Response has been canceled.';
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the response and all its network handles.
|
||||
*/
|
||||
protected function close(): void
|
||||
{
|
||||
$this->canary->cancel();
|
||||
$this->inflate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds pending responses to the activity list.
|
||||
*/
|
||||
abstract protected static function schedule(self $response, array &$runningResponses): void;
|
||||
|
||||
/**
|
||||
* Performs all pending non-blocking operations.
|
||||
*/
|
||||
abstract protected static function perform(ClientState $multi, array &$responses): void;
|
||||
|
||||
/**
|
||||
* Waits for network activity.
|
||||
*/
|
||||
abstract protected static function select(ClientState $multi, float $timeout): int;
|
||||
|
||||
private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headers, string &$debug = ''): void
|
||||
{
|
||||
foreach ($responseHeaders as $h) {
|
||||
if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? (\d\d\d)(?: |$)#', $h, $m)) {
|
||||
if ($headers) {
|
||||
$debug .= "< \r\n";
|
||||
$headers = [];
|
||||
}
|
||||
$info['http_code'] = (int) $m[1];
|
||||
} elseif (2 === \count($m = explode(':', $h, 2))) {
|
||||
$headers[strtolower($m[0])][] = ltrim($m[1]);
|
||||
}
|
||||
|
||||
$debug .= "< {$h}\r\n";
|
||||
$info['response_headers'][] = $h;
|
||||
}
|
||||
|
||||
$debug .= "< \r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the request is always sent and that the response code was checked.
|
||||
*/
|
||||
private function doDestruct()
|
||||
{
|
||||
$this->shouldBuffer = true;
|
||||
|
||||
if ($this->initializer && null === $this->info['error']) {
|
||||
self::initialize($this);
|
||||
$this->checkStatusCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an event loop based on a buffer activity queue.
|
||||
*
|
||||
* @param iterable<array-key, self> $responses
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function stream(iterable $responses, ?float $timeout = null): \Generator
|
||||
{
|
||||
$runningResponses = [];
|
||||
|
||||
foreach ($responses as $response) {
|
||||
self::schedule($response, $runningResponses);
|
||||
}
|
||||
|
||||
$lastActivity = microtime(true);
|
||||
$elapsedTimeout = 0;
|
||||
|
||||
if ($fromLastTimeout = 0.0 === $timeout && '-0' === (string) $timeout) {
|
||||
$timeout = null;
|
||||
} elseif ($fromLastTimeout = 0 > $timeout) {
|
||||
$timeout = -$timeout;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
$hasActivity = false;
|
||||
$timeoutMax = 0;
|
||||
$timeoutMin = $timeout ?? \INF;
|
||||
|
||||
/** @var ClientState $multi */
|
||||
foreach ($runningResponses as $i => [$multi]) {
|
||||
$responses = &$runningResponses[$i][1];
|
||||
self::perform($multi, $responses);
|
||||
|
||||
foreach ($responses as $j => $response) {
|
||||
$timeoutMax = $timeout ?? max($timeoutMax, $response->timeout);
|
||||
$timeoutMin = min($timeoutMin, $response->timeout, 1);
|
||||
$chunk = false;
|
||||
|
||||
if ($fromLastTimeout && null !== $multi->lastTimeout) {
|
||||
$elapsedTimeout = microtime(true) - $multi->lastTimeout;
|
||||
}
|
||||
|
||||
if (isset($multi->handlesActivity[$j])) {
|
||||
$multi->lastTimeout = null;
|
||||
} elseif (!isset($multi->openHandles[$j])) {
|
||||
unset($responses[$j]);
|
||||
continue;
|
||||
} elseif ($elapsedTimeout >= $timeoutMax) {
|
||||
$multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))];
|
||||
$multi->lastTimeout ?? $multi->lastTimeout = $lastActivity;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
while ($multi->handlesActivity[$j] ?? false) {
|
||||
$hasActivity = true;
|
||||
$elapsedTimeout = 0;
|
||||
|
||||
if (\is_string($chunk = array_shift($multi->handlesActivity[$j]))) {
|
||||
if (null !== $response->inflate && false === $chunk = @inflate_add($response->inflate, $chunk)) {
|
||||
$multi->handlesActivity[$j] = [null, new TransportException(sprintf('Error while processing content unencoding for "%s".', $response->getInfo('url')))];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content, $chunk)) {
|
||||
$multi->handlesActivity[$j] = [null, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($chunk)))];
|
||||
continue;
|
||||
}
|
||||
|
||||
$chunkLen = \strlen($chunk);
|
||||
$chunk = new DataChunk($response->offset, $chunk);
|
||||
$response->offset += $chunkLen;
|
||||
} elseif (null === $chunk) {
|
||||
$e = $multi->handlesActivity[$j][0];
|
||||
unset($responses[$j], $multi->handlesActivity[$j]);
|
||||
$response->close();
|
||||
|
||||
if (null !== $e) {
|
||||
$response->info['error'] = $e->getMessage();
|
||||
|
||||
if ($e instanceof \Error) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$chunk = new ErrorChunk($response->offset, $e);
|
||||
} else {
|
||||
if (0 === $response->offset && null === $response->content) {
|
||||
$response->content = fopen('php://memory', 'w+');
|
||||
}
|
||||
|
||||
$chunk = new LastChunk($response->offset);
|
||||
}
|
||||
} elseif ($chunk instanceof ErrorChunk) {
|
||||
unset($responses[$j]);
|
||||
$elapsedTimeout = $timeoutMax;
|
||||
} elseif ($chunk instanceof FirstChunk) {
|
||||
if ($response->logger) {
|
||||
$info = $response->getInfo();
|
||||
$response->logger->info(sprintf('Response: "%s %s"', $info['http_code'], $info['url']));
|
||||
}
|
||||
|
||||
$response->inflate = \extension_loaded('zlib') && $response->inflate && 'gzip' === ($response->headers['content-encoding'][0] ?? null) ? inflate_init(\ZLIB_ENCODING_GZIP) : null;
|
||||
|
||||
if ($response->shouldBuffer instanceof \Closure) {
|
||||
try {
|
||||
$response->shouldBuffer = ($response->shouldBuffer)($response->headers);
|
||||
|
||||
if (null !== $response->info['error']) {
|
||||
throw new TransportException($response->info['error']);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$response->close();
|
||||
$multi->handlesActivity[$j] = [null, $e];
|
||||
}
|
||||
}
|
||||
|
||||
if (true === $response->shouldBuffer) {
|
||||
$response->content = fopen('php://temp', 'w+');
|
||||
} elseif (\is_resource($response->shouldBuffer)) {
|
||||
$response->content = $response->shouldBuffer;
|
||||
}
|
||||
$response->shouldBuffer = null;
|
||||
|
||||
yield $response => $chunk;
|
||||
|
||||
if ($response->initializer && null === $response->info['error']) {
|
||||
// Ensure the HTTP status code is always checked
|
||||
$response->getHeaders(true);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
yield $response => $chunk;
|
||||
}
|
||||
|
||||
unset($multi->handlesActivity[$j]);
|
||||
|
||||
if ($chunk instanceof ErrorChunk && !$chunk->didThrow()) {
|
||||
// Ensure transport exceptions are always thrown
|
||||
$chunk->getContent();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$responses) {
|
||||
unset($runningResponses[$i]);
|
||||
}
|
||||
|
||||
// Prevent memory leaks
|
||||
$multi->handlesActivity = $multi->handlesActivity ?: [];
|
||||
$multi->openHandles = $multi->openHandles ?: [];
|
||||
}
|
||||
|
||||
if (!$runningResponses) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($hasActivity) {
|
||||
$lastActivity = microtime(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (-1 === self::select($multi, min($timeoutMin, $timeoutMax - $elapsedTimeout))) {
|
||||
usleep((int) min(500, 1E6 * $timeoutMin));
|
||||
}
|
||||
|
||||
$elapsedTimeout = microtime(true) - $lastActivity;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue