refactor: Cleanup git state - commit all staged changes
Major refactoring cleanup: - Add new controller architecture (class-controller-*.php) - Add new settings-v2 UI (views/settings-v2/) - Add new CSS architecture (agentic-sidebar.css, tokens) - Add esbuild build pipeline (scripts/build.js, package.json) - Add composer dependencies (vendor/) - Add frontend src directory (assets/js/src/index.jsx) - Add documentation files - Remove old/obsolete files (class-settings.php, old CSS) This commits all pending changes from previous refactoring efforts.
This commit is contained in:
150
vendor/symfony/http-client/Internal/AmpBody.php
vendored
Normal file
150
vendor/symfony/http-client/Internal/AmpBody.php
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
<?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\Internal;
|
||||
|
||||
use Amp\ByteStream\ReadableBuffer;
|
||||
use Amp\ByteStream\ReadableIterableStream;
|
||||
use Amp\ByteStream\ReadableResourceStream;
|
||||
use Amp\ByteStream\ReadableStream;
|
||||
use Amp\Cancellation;
|
||||
use Amp\Http\Client\HttpContent;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AmpBody implements HttpContent, ReadableStream, \IteratorAggregate
|
||||
{
|
||||
private ReadableStream $body;
|
||||
private ?string $content;
|
||||
private array $info;
|
||||
private ?int $offset = 0;
|
||||
private int $length = -1;
|
||||
private ?int $uploaded = null;
|
||||
|
||||
/**
|
||||
* @param \Closure|resource|string $body
|
||||
*/
|
||||
public function __construct(
|
||||
$body,
|
||||
&$info,
|
||||
private \Closure $onProgress,
|
||||
) {
|
||||
$this->info = &$info;
|
||||
|
||||
if (\is_resource($body)) {
|
||||
$this->offset = ftell($body);
|
||||
$this->length = fstat($body)['size'];
|
||||
$this->body = new ReadableResourceStream($body);
|
||||
} elseif (\is_string($body)) {
|
||||
$this->length = \strlen($body);
|
||||
$this->body = new ReadableBuffer($body);
|
||||
$this->content = $body;
|
||||
} else {
|
||||
$this->body = new ReadableIterableStream((static function () use ($body) {
|
||||
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)));
|
||||
}
|
||||
|
||||
yield $data;
|
||||
}
|
||||
})());
|
||||
}
|
||||
}
|
||||
|
||||
public function getContent(): ReadableStream
|
||||
{
|
||||
if (null !== $this->uploaded) {
|
||||
$this->uploaded = null;
|
||||
|
||||
if (\is_string($this->body)) {
|
||||
$this->offset = 0;
|
||||
} elseif ($this->body instanceof ReadableResourceStream) {
|
||||
fseek($this->body->getResource(), $this->offset);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContentType(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getContentLength(): ?int
|
||||
{
|
||||
return 0 <= $this->length ? $this->length - $this->offset : null;
|
||||
}
|
||||
|
||||
public function read(?Cancellation $cancellation = null): ?string
|
||||
{
|
||||
$this->info['size_upload'] += $this->uploaded;
|
||||
$this->uploaded = 0;
|
||||
($this->onProgress)();
|
||||
|
||||
if (null !== $data = $this->body->read($cancellation)) {
|
||||
$this->uploaded = \strlen($data);
|
||||
} else {
|
||||
$this->info['upload_content_length'] = $this->info['size_upload'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return $this->body->isReadable();
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
$this->body->close();
|
||||
}
|
||||
|
||||
public function isClosed(): bool
|
||||
{
|
||||
return $this->body->isClosed();
|
||||
}
|
||||
|
||||
public function onClose(\Closure $onClose): void
|
||||
{
|
||||
$this->body->onClose($onClose);
|
||||
}
|
||||
|
||||
public function getIterator(): \Traversable
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public static function rewind(HttpContent $body): HttpContent
|
||||
{
|
||||
if (!$body instanceof self) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$body->uploaded = null;
|
||||
|
||||
if ($body->body instanceof ReadableResourceStream && !$body->body->isClosed()) {
|
||||
fseek($body->body->getResource(), $body->offset);
|
||||
}
|
||||
|
||||
if ($body->body instanceof ReadableBuffer) {
|
||||
return new $body($body->content, $body->info, $body->onProgress);
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
204
vendor/symfony/http-client/Internal/AmpClientState.php
vendored
Normal file
204
vendor/symfony/http-client/Internal/AmpClientState.php
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
<?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\Internal;
|
||||
|
||||
use Amp\ByteStream\ResourceStream;
|
||||
use Amp\Cancellation;
|
||||
use Amp\DeferredCancellation;
|
||||
use Amp\DeferredFuture;
|
||||
use Amp\Future;
|
||||
use Amp\Http\Client\Connection\ConnectionLimitingPool;
|
||||
use Amp\Http\Client\Connection\DefaultConnectionFactory;
|
||||
use Amp\Http\Client\InterceptedHttpClient;
|
||||
use Amp\Http\Client\Interceptor\RetryRequests;
|
||||
use Amp\Http\Client\PooledHttpClient;
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Http\Client\Response;
|
||||
use Amp\Http\Tunnel\Http1TunnelConnector;
|
||||
use Amp\Http\Tunnel\Https1TunnelConnector;
|
||||
use Amp\Socket\Certificate;
|
||||
use Amp\Socket\ClientTlsContext;
|
||||
use Amp\Socket\ConnectContext;
|
||||
use Amp\Socket\DnsSocketConnector;
|
||||
use Amp\Socket\InternetAddress;
|
||||
use Amp\Socket\Socket;
|
||||
use Amp\Socket\SocketAddress;
|
||||
use Amp\Socket\SocketConnector;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Internal representation of the Amp client's state.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class AmpClientState extends ClientState
|
||||
{
|
||||
public array $dnsCache = [];
|
||||
public int $responseCount = 0;
|
||||
public array $pushedResponses = [];
|
||||
|
||||
private array $clients = [];
|
||||
private \Closure $clientConfigurator;
|
||||
|
||||
public function __construct(
|
||||
?callable $clientConfigurator,
|
||||
private int $maxHostConnections,
|
||||
private int $maxPendingPushes,
|
||||
private ?LoggerInterface &$logger,
|
||||
) {
|
||||
$clientConfigurator ??= static fn (PooledHttpClient $client) => new InterceptedHttpClient($client, new RetryRequests(2), []);
|
||||
$this->clientConfigurator = $clientConfigurator(...);
|
||||
}
|
||||
|
||||
public function request(array $options, Request $request, DeferredCancellation $canceller, array &$info, \Closure $onProgress, &$handle): Response
|
||||
{
|
||||
if ($options['proxy']) {
|
||||
if ($request->hasHeader('proxy-authorization')) {
|
||||
$options['proxy']['auth'] = $request->getHeader('proxy-authorization');
|
||||
}
|
||||
|
||||
// Matching "no_proxy" should follow the behavior of curl
|
||||
$host = $request->getUri()->getHost();
|
||||
foreach ($options['proxy']['no_proxy'] as $rule) {
|
||||
$dotRule = '.'.ltrim($rule, '.');
|
||||
|
||||
if ('*' === $rule || $host === $rule || str_ends_with($host, $dotRule)) {
|
||||
$options['proxy'] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->hasHeader('proxy-authorization')) {
|
||||
$request->removeHeader('proxy-authorization');
|
||||
}
|
||||
|
||||
if ($options['capture_peer_cert_chain']) {
|
||||
$info['peer_certificate_chain'] = [];
|
||||
}
|
||||
|
||||
$request->addEventListener(new AmpListener($info, $options['peer_fingerprint']['pin-sha256'] ?? [], $onProgress, $handle, $options['max_connect_duration'], $canceller));
|
||||
$request->setPushHandler(fn ($request, $response) => $this->handlePush($request, $response, $options));
|
||||
|
||||
if (0 <= $bodySize = $request->hasHeader('content-length') ? (int) $request->getHeader('content-length') : $request->getBody()->getContentLength() ?? -1) {
|
||||
$info['upload_content_length'] = ((1 + $info['upload_content_length']) ?? 1) - 1 + $bodySize;
|
||||
}
|
||||
|
||||
[$client, $connector] = $this->getClient($options);
|
||||
$response = $client->request($request, $canceller->getCancellation());
|
||||
$handle = $connector->handle;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function getClient(array $options): array
|
||||
{
|
||||
$options = [
|
||||
'bindto' => $options['bindto'] ?: '0',
|
||||
'verify_peer' => $options['verify_peer'],
|
||||
'capath' => $options['capath'],
|
||||
'cafile' => $options['cafile'],
|
||||
'local_cert' => $options['local_cert'],
|
||||
'local_pk' => $options['local_pk'],
|
||||
'ciphers' => $options['ciphers'],
|
||||
'capture_peer_cert_chain' => $options['capture_peer_cert_chain'] || $options['peer_fingerprint'],
|
||||
'proxy' => $options['proxy'],
|
||||
'crypto_method' => $options['crypto_method'],
|
||||
];
|
||||
|
||||
$key = hash('xxh128', serialize($options));
|
||||
|
||||
if (isset($this->clients[$key])) {
|
||||
return $this->clients[$key];
|
||||
}
|
||||
|
||||
$context = new ClientTlsContext('');
|
||||
$options['verify_peer'] || $context = $context->withoutPeerVerification();
|
||||
$options['cafile'] && $context = $context->withCaFile($options['cafile']);
|
||||
$options['capath'] && $context = $context->withCaPath($options['capath']);
|
||||
$options['local_cert'] && $context = $context->withCertificate(new Certificate($options['local_cert'], $options['local_pk']));
|
||||
$options['ciphers'] && $context = $context->withCiphers($options['ciphers']);
|
||||
$options['capture_peer_cert_chain'] && $context = $context->withPeerCapturing();
|
||||
$options['crypto_method'] && $context = $context->withMinimumVersion($options['crypto_method']);
|
||||
|
||||
$connector = $handleConnector = new class implements SocketConnector {
|
||||
public DnsSocketConnector $connector;
|
||||
public string $uri;
|
||||
/** @var resource|null */
|
||||
public $handle;
|
||||
|
||||
public function connect(SocketAddress|string $uri, ?ConnectContext $context = null, ?Cancellation $cancellation = null): Socket
|
||||
{
|
||||
$socket = $this->connector->connect($this->uri ?? $uri, $context, $cancellation);
|
||||
$this->handle = $socket instanceof ResourceStream ? $socket->getResource() : false;
|
||||
|
||||
return $socket;
|
||||
}
|
||||
};
|
||||
$connector->connector = new DnsSocketConnector(new AmpResolver($this->dnsCache));
|
||||
|
||||
$context = (new ConnectContext())
|
||||
->withTcpNoDelay()
|
||||
->withTlsContext($context);
|
||||
|
||||
if ($options['bindto']) {
|
||||
if (file_exists($options['bindto'])) {
|
||||
$connector->uri = 'unix://'.$options['bindto'];
|
||||
} else {
|
||||
$context = $context->withBindTo($options['bindto']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['proxy']) {
|
||||
$proxyUrl = parse_url($options['proxy']['url']);
|
||||
$proxySocket = new InternetAddress($proxyUrl['host'], $proxyUrl['port']);
|
||||
$proxyHeaders = $options['proxy']['auth'] ? ['Proxy-Authorization' => $options['proxy']['auth']] : [];
|
||||
|
||||
if ('ssl' === $proxyUrl['scheme']) {
|
||||
$connector = new Https1TunnelConnector($proxySocket, $context->getTlsContext(), $proxyHeaders, $connector);
|
||||
} else {
|
||||
$connector = new Http1TunnelConnector($proxySocket, $proxyHeaders, $connector);
|
||||
}
|
||||
}
|
||||
|
||||
$maxHostConnections = 0 < $this->maxHostConnections ? $this->maxHostConnections : \PHP_INT_MAX;
|
||||
$pool = new DefaultConnectionFactory($connector, $context);
|
||||
$pool = ConnectionLimitingPool::byAuthority($maxHostConnections, $pool);
|
||||
|
||||
return $this->clients[$key] = [($this->clientConfigurator)(new PooledHttpClient($pool)), $handleConnector];
|
||||
}
|
||||
|
||||
private function handlePush(Request $request, Future $response, array $options): void
|
||||
{
|
||||
$deferred = new DeferredFuture();
|
||||
$authority = $request->getUri()->getAuthority();
|
||||
|
||||
if ($this->maxPendingPushes <= \count($this->pushedResponses[$authority] ?? [])) {
|
||||
$fifoUrl = key($this->pushedResponses[$authority]);
|
||||
unset($this->pushedResponses[$authority][$fifoUrl]);
|
||||
$this->logger?->debug(\sprintf('Evicting oldest pushed response: "%s"', $fifoUrl));
|
||||
}
|
||||
|
||||
$url = (string) $request->getUri();
|
||||
$this->logger?->debug(\sprintf('Queueing pushed response: "%s"', $url));
|
||||
$this->pushedResponses[$authority][] = [$url, $deferred, $request, $response, [
|
||||
'proxy' => $options['proxy'],
|
||||
'bindto' => $options['bindto'],
|
||||
'local_cert' => $options['local_cert'],
|
||||
'local_pk' => $options['local_pk'],
|
||||
]];
|
||||
|
||||
$deferred->getFuture()->await();
|
||||
}
|
||||
}
|
||||
234
vendor/symfony/http-client/Internal/AmpListener.php
vendored
Normal file
234
vendor/symfony/http-client/Internal/AmpListener.php
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
<?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\Internal;
|
||||
|
||||
use Amp\DeferredCancellation;
|
||||
use Amp\Http\Client\ApplicationInterceptor;
|
||||
use Amp\Http\Client\Connection\Connection;
|
||||
use Amp\Http\Client\Connection\Stream;
|
||||
use Amp\Http\Client\EventListener;
|
||||
use Amp\Http\Client\NetworkInterceptor;
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Http\Client\Response;
|
||||
use Amp\Socket\InternetAddress;
|
||||
use Revolt\EventLoop;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AmpListener implements EventListener
|
||||
{
|
||||
private array $info;
|
||||
private ?string $connectTimerId = null;
|
||||
|
||||
/**
|
||||
* @param resource|null $handle
|
||||
*/
|
||||
public function __construct(
|
||||
array &$info,
|
||||
private array $pinSha256,
|
||||
private \Closure $onProgress,
|
||||
private &$handle,
|
||||
private float $maxConnectDuration,
|
||||
private DeferredCancellation $canceller,
|
||||
) {
|
||||
$info += [
|
||||
'connect_time' => 0.0,
|
||||
'pretransfer_time' => 0.0,
|
||||
'starttransfer_time' => 0.0,
|
||||
'total_time' => 0.0,
|
||||
'namelookup_time' => 0.0,
|
||||
'primary_ip' => '',
|
||||
'primary_port' => 0,
|
||||
];
|
||||
|
||||
$this->info = &$info;
|
||||
}
|
||||
|
||||
public function requestStart(Request $request): void
|
||||
{
|
||||
$this->info['start_time'] ??= microtime(true);
|
||||
|
||||
if (0 < $this->maxConnectDuration) {
|
||||
$this->connectTimerId = EventLoop::delay($this->maxConnectDuration, function (): void {
|
||||
$this->canceller->cancel(new TransportException(\sprintf('Max connect duration was reached for "%s".', $this->info['url'])));
|
||||
});
|
||||
}
|
||||
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function connectionAcquired(Request $request, Connection $connection, int $streamCount): void
|
||||
{
|
||||
if (null !== $this->connectTimerId) {
|
||||
EventLoop::cancel($this->connectTimerId);
|
||||
$this->connectTimerId = null;
|
||||
}
|
||||
|
||||
$this->info['namelookup_time'] = microtime(true) - $this->info['start_time']; // see https://github.com/amphp/socket/issues/114
|
||||
$this->info['connect_time'] = microtime(true) - $this->info['start_time'];
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function requestHeaderStart(Request $request, Stream $stream): void
|
||||
{
|
||||
$host = $stream->getRemoteAddress()->toString();
|
||||
if ($stream->getRemoteAddress() instanceof InternetAddress) {
|
||||
$host = $stream->getRemoteAddress()->getAddress();
|
||||
$this->info['primary_port'] = $stream->getRemoteAddress()->getPort();
|
||||
}
|
||||
|
||||
$this->info['primary_ip'] = $host;
|
||||
|
||||
if (str_contains($host, ':')) {
|
||||
$host = '['.$host.']';
|
||||
}
|
||||
|
||||
$this->info['pretransfer_time'] = microtime(true) - $this->info['start_time'];
|
||||
$this->info['debug'] .= \sprintf("* Connected to %s (%s) port %d\n", $request->getUri()->getHost(), $host, $this->info['primary_port']);
|
||||
|
||||
if ((isset($this->info['peer_certificate_chain']) || $this->pinSha256) && null !== $tlsInfo = $stream->getTlsInfo()) {
|
||||
foreach ($tlsInfo->getPeerCertificates() as $cert) {
|
||||
$this->info['peer_certificate_chain'][] = openssl_x509_read($cert->toPem());
|
||||
}
|
||||
|
||||
if ($this->pinSha256) {
|
||||
$pin = openssl_pkey_get_public($this->info['peer_certificate_chain'][0]);
|
||||
$pin = openssl_pkey_get_details($pin)['key'];
|
||||
$pin = \array_slice(explode("\n", $pin), 1, -2);
|
||||
$pin = base64_decode(implode('', $pin));
|
||||
$pin = base64_encode(hash('sha256', $pin, true));
|
||||
|
||||
if (!\in_array($pin, $this->pinSha256, true)) {
|
||||
throw new TransportException(\sprintf('SSL public key does not match pinned public key for "%s".', $this->info['url']));
|
||||
}
|
||||
}
|
||||
}
|
||||
($this->onProgress)();
|
||||
|
||||
$uri = $request->getUri();
|
||||
$requestUri = $uri->getPath() ?: '/';
|
||||
|
||||
if ('' !== $query = $uri->getQuery()) {
|
||||
$requestUri .= '?'.$query;
|
||||
}
|
||||
|
||||
if ('CONNECT' === $method = $request->getMethod()) {
|
||||
$requestUri = $uri->getHost().': '.($uri->getPort() ?? ('https' === $uri->getScheme() ? 443 : 80));
|
||||
}
|
||||
|
||||
$this->info['debug'] .= \sprintf("> %s %s HTTP/%s \r\n", $method, $requestUri, $request->getProtocolVersions()[0]);
|
||||
|
||||
foreach ($request->getHeaderPairs() as [$name, $value]) {
|
||||
$this->info['debug'] .= $name.': '.$value."\r\n";
|
||||
}
|
||||
$this->info['debug'] .= "\r\n";
|
||||
}
|
||||
|
||||
public function requestBodyEnd(Request $request, Stream $stream): void
|
||||
{
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function responseHeaderStart(Request $request, Stream $stream): void
|
||||
{
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function requestEnd(Request $request, Response $response): void
|
||||
{
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function requestFailed(Request $request, \Throwable $exception): void
|
||||
{
|
||||
if (null !== $this->connectTimerId) {
|
||||
EventLoop::cancel($this->connectTimerId);
|
||||
$this->connectTimerId = null;
|
||||
}
|
||||
|
||||
$this->handle = null;
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function requestHeaderEnd(Request $request, Stream $stream): void
|
||||
{
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function requestBodyStart(Request $request, Stream $stream): void
|
||||
{
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function requestBodyProgress(Request $request, Stream $stream): void
|
||||
{
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function responseHeaderEnd(Request $request, Stream $stream, Response $response): void
|
||||
{
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function responseBodyStart(Request $request, Stream $stream, Response $response): void
|
||||
{
|
||||
$this->info['starttransfer_time'] = microtime(true) - $this->info['start_time'];
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function responseBodyProgress(Request $request, Stream $stream, Response $response): void
|
||||
{
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function responseBodyEnd(Request $request, Stream $stream, Response $response): void
|
||||
{
|
||||
$this->handle = null;
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function applicationInterceptorStart(Request $request, ApplicationInterceptor $interceptor): void
|
||||
{
|
||||
}
|
||||
|
||||
public function applicationInterceptorEnd(Request $request, ApplicationInterceptor $interceptor, Response $response): void
|
||||
{
|
||||
}
|
||||
|
||||
public function networkInterceptorStart(Request $request, NetworkInterceptor $interceptor): void
|
||||
{
|
||||
}
|
||||
|
||||
public function networkInterceptorEnd(Request $request, NetworkInterceptor $interceptor, Response $response): void
|
||||
{
|
||||
}
|
||||
|
||||
public function push(Request $request): void
|
||||
{
|
||||
($this->onProgress)();
|
||||
}
|
||||
|
||||
public function requestRejected(Request $request): void
|
||||
{
|
||||
if (null !== $this->connectTimerId) {
|
||||
EventLoop::cancel($this->connectTimerId);
|
||||
$this->connectTimerId = null;
|
||||
}
|
||||
|
||||
$this->handle = null;
|
||||
($this->onProgress)();
|
||||
}
|
||||
}
|
||||
64
vendor/symfony/http-client/Internal/AmpResolver.php
vendored
Normal file
64
vendor/symfony/http-client/Internal/AmpResolver.php
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
<?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\Internal;
|
||||
|
||||
use Amp\Cancellation;
|
||||
use Amp\Dns;
|
||||
use Amp\Dns\DnsRecord;
|
||||
use Amp\Dns\DnsResolver;
|
||||
|
||||
/**
|
||||
* Handles local overrides for the DNS resolver.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AmpResolver implements DnsResolver
|
||||
{
|
||||
public function __construct(
|
||||
private array &$dnsMap,
|
||||
) {
|
||||
}
|
||||
|
||||
public function resolve(string $name, ?int $typeRestriction = null, ?Cancellation $cancellation = null): array
|
||||
{
|
||||
$recordType = DnsRecord::A;
|
||||
$ip = $this->dnsMap[$name] ?? null;
|
||||
|
||||
if (null !== $ip && str_contains($ip, ':')) {
|
||||
$recordType = DnsRecord::AAAA;
|
||||
}
|
||||
|
||||
if (null === $ip || $recordType !== ($typeRestriction ?? $recordType)) {
|
||||
return Dns\resolve($name, $typeRestriction, $cancellation);
|
||||
}
|
||||
|
||||
return [new DnsRecord($ip, $recordType, null)];
|
||||
}
|
||||
|
||||
public function query(string $name, int $type, ?Cancellation $cancellation = null): array
|
||||
{
|
||||
$recordType = DnsRecord::A;
|
||||
$ip = $this->dnsMap[$name] ?? null;
|
||||
|
||||
if (null !== $ip && str_contains($ip, ':')) {
|
||||
$recordType = DnsRecord::AAAA;
|
||||
}
|
||||
|
||||
if (null !== $ip || $recordType !== $type) {
|
||||
return Dns\resolve($name, $type, $cancellation);
|
||||
}
|
||||
|
||||
return [new DnsRecord($ip, $recordType, null)];
|
||||
}
|
||||
}
|
||||
39
vendor/symfony/http-client/Internal/Canary.php
vendored
Normal file
39
vendor/symfony/http-client/Internal/Canary.php
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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\Internal;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Canary
|
||||
{
|
||||
public function __construct(
|
||||
private \Closure $canceller,
|
||||
) {
|
||||
}
|
||||
|
||||
public function cancel(): void
|
||||
{
|
||||
if (isset($this->canceller)) {
|
||||
$canceller = $this->canceller;
|
||||
unset($this->canceller);
|
||||
$canceller();
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->cancel();
|
||||
}
|
||||
}
|
||||
26
vendor/symfony/http-client/Internal/ClientState.php
vendored
Normal file
26
vendor/symfony/http-client/Internal/ClientState.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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\Internal;
|
||||
|
||||
/**
|
||||
* Internal representation of the client state.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ClientState
|
||||
{
|
||||
public array $handlesActivity = [];
|
||||
public array $openHandles = [];
|
||||
public ?float $lastTimeout = null;
|
||||
}
|
||||
183
vendor/symfony/http-client/Internal/CurlClientState.php
vendored
Normal file
183
vendor/symfony/http-client/Internal/CurlClientState.php
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
<?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\Internal;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\Response\CurlResponse;
|
||||
|
||||
/**
|
||||
* Internal representation of the cURL client's state.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CurlClientState extends ClientState
|
||||
{
|
||||
public ?\CurlMultiHandle $handle;
|
||||
public ?\CurlShareHandle $share;
|
||||
public \CurlShareHandle|\CurlSharePersistentHandle|null $persistentShare;
|
||||
public bool $performing = false;
|
||||
|
||||
/** @var PushedResponse[] */
|
||||
public array $pushedResponses = [];
|
||||
public DnsCache $dnsCache;
|
||||
/** @var float[] */
|
||||
public array $pauseExpiries = [];
|
||||
public int $execCounter = \PHP_INT_MIN;
|
||||
public ?LoggerInterface $logger = null;
|
||||
|
||||
/** @var array<string, true> Indexed by self::originKey() */
|
||||
public array $ntlmRequiresFreshConnection = [];
|
||||
|
||||
public static array $curlVersion;
|
||||
|
||||
public function __construct(
|
||||
private int $maxHostConnections,
|
||||
private int $maxPendingPushes,
|
||||
) {
|
||||
self::$curlVersion ??= curl_version();
|
||||
$this->dnsCache = new DnsCache();
|
||||
|
||||
// handle, share and persistentShare are initialized lazily in __get()
|
||||
unset($this->handle, $this->share, $this->persistentShare);
|
||||
}
|
||||
|
||||
public static function originKey(string $scheme, string $host, ?int $port = null): string
|
||||
{
|
||||
$scheme = strtolower(rtrim($scheme, ':'));
|
||||
$port ??= 'https' === $scheme ? 443 : 80;
|
||||
|
||||
return $scheme.'://'.strtolower($host).':'.$port;
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
foreach ($this->pushedResponses as $url => $response) {
|
||||
$this->logger?->debug(\sprintf('Unused pushed response: "%s"', $url));
|
||||
curl_multi_remove_handle($this->handle, $response->handle);
|
||||
unset($this->handlesActivity[(int) $response->handle]);
|
||||
}
|
||||
|
||||
$this->pushedResponses = [];
|
||||
$this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals;
|
||||
$this->dnsCache->removals = $this->dnsCache->hostnames = [];
|
||||
$this->ntlmRequiresFreshConnection = [];
|
||||
|
||||
unset($this->share);
|
||||
}
|
||||
|
||||
public function __get(string $name): mixed
|
||||
{
|
||||
if ('persistentShare' === $name) {
|
||||
if (\PHP_VERSION_ID < 80500) {
|
||||
return $this->persistentShare = $this->share;
|
||||
}
|
||||
|
||||
return $this->persistentShare = curl_share_init_persistent([
|
||||
\CURL_LOCK_DATA_DNS,
|
||||
\CURL_LOCK_DATA_SSL_SESSION,
|
||||
\CURL_LOCK_DATA_CONNECT,
|
||||
]);
|
||||
}
|
||||
|
||||
if ('share' === $name) {
|
||||
$this->share = curl_share_init();
|
||||
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS);
|
||||
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION);
|
||||
|
||||
// Don't share CURL_LOCK_DATA_CONNECT: easy handles attached to the same multi handle
|
||||
// already share the connection cache, and adding it here creates a second pool that
|
||||
// bypasses CURLMOPT_MAX_HOST_CONNECTIONS.
|
||||
// See https://curl.se/libcurl/c/CURLSHOPT_SHARE.html#CURLLOCKDATACONNECT
|
||||
|
||||
return $this->share;
|
||||
}
|
||||
|
||||
if ('handle' === $name) {
|
||||
$this->handle = curl_multi_init();
|
||||
|
||||
// Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
|
||||
if (\defined('CURLPIPE_MULTIPLEX')) {
|
||||
curl_multi_setopt($this->handle, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX);
|
||||
}
|
||||
$maxHostConnections = $this->maxHostConnections;
|
||||
if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS') && 0 < $maxHostConnections) {
|
||||
$maxHostConnections = curl_multi_setopt($this->handle, \CURLMOPT_MAX_HOST_CONNECTIONS, $maxHostConnections) ? min(50 * $maxHostConnections, 4294967295) : $maxHostConnections;
|
||||
}
|
||||
if (\defined('CURLMOPT_MAXCONNECTS') && 0 < $maxHostConnections) {
|
||||
curl_multi_setopt($this->handle, \CURLMOPT_MAXCONNECTS, $maxHostConnections);
|
||||
}
|
||||
|
||||
// Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535
|
||||
if (0 < $this->maxPendingPushes && (\defined('CURLMOPT_PUSHFUNCTION') && 0x073D00 <= self::$curlVersion['version_number'] && (\CURL_VERSION_HTTP2 & self::$curlVersion['features']))) {
|
||||
// Clone to prevent a circular reference
|
||||
$multi = clone $this;
|
||||
$multi->handle = null;
|
||||
$multi->share = null;
|
||||
$multi->persistentShare = null;
|
||||
$multi->pushedResponses = &$this->pushedResponses;
|
||||
$multi->logger = &$this->logger;
|
||||
$multi->handlesActivity = &$this->handlesActivity;
|
||||
$multi->openHandles = &$this->openHandles;
|
||||
|
||||
curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, $multi->handlePush(...));
|
||||
}
|
||||
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
throw new \LogicException(\sprintf('Unknown property "%s" on "%s".', $name, self::class));
|
||||
}
|
||||
|
||||
private function handlePush($parent, $pushed, array $requestHeaders): int
|
||||
{
|
||||
$headers = [];
|
||||
$origin = curl_getinfo($parent, \CURLINFO_EFFECTIVE_URL);
|
||||
|
||||
foreach ($requestHeaders as $h) {
|
||||
if (false !== $i = strpos($h, ':', 1)) {
|
||||
$headers[substr($h, 0, $i)][] = substr($h, 1 + $i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($headers[':method']) || !isset($headers[':scheme']) || !isset($headers[':authority']) || !isset($headers[':path'])) {
|
||||
$this->logger?->debug(\sprintf('Rejecting pushed response from "%s": pushed headers are invalid', $origin));
|
||||
|
||||
return \CURL_PUSH_DENY;
|
||||
}
|
||||
|
||||
$url = $headers[':scheme'][0].'://'.$headers[':authority'][0];
|
||||
|
||||
// curl before 7.65 doesn't validate the pushed ":authority" header,
|
||||
// but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host,
|
||||
// ignoring domains mentioned as alt-name in the certificate for now (same as curl).
|
||||
if (!str_starts_with($origin, $url.'/')) {
|
||||
$this->logger?->debug(\sprintf('Rejecting pushed response from "%s": server is not authoritative for "%s"', $origin, $url));
|
||||
|
||||
return \CURL_PUSH_DENY;
|
||||
}
|
||||
|
||||
if ($this->maxPendingPushes <= \count($this->pushedResponses)) {
|
||||
$fifoUrl = key($this->pushedResponses);
|
||||
unset($this->pushedResponses[$fifoUrl]);
|
||||
$this->logger?->debug(\sprintf('Evicting oldest pushed response: "%s"', $fifoUrl));
|
||||
}
|
||||
|
||||
$url .= $headers[':path'][0];
|
||||
$this->logger?->debug(\sprintf('Queueing pushed response: "%s"', $url));
|
||||
|
||||
$this->pushedResponses[$url] = new PushedResponse(new CurlResponse($this, $pushed), $headers, $this->openHandles[(int) $parent][1] ?? [], $pushed);
|
||||
|
||||
return \CURL_PUSH_OK;
|
||||
}
|
||||
}
|
||||
39
vendor/symfony/http-client/Internal/DnsCache.php
vendored
Normal file
39
vendor/symfony/http-client/Internal/DnsCache.php
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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\Internal;
|
||||
|
||||
/**
|
||||
* Cache for resolved DNS queries.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class DnsCache
|
||||
{
|
||||
/**
|
||||
* Resolved hostnames (hostname => IP address).
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public array $hostnames = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public array $removals = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public array $evictions = [];
|
||||
}
|
||||
97
vendor/symfony/http-client/Internal/FollowRedirectsTrait.php
vendored
Normal file
97
vendor/symfony/http-client/Internal/FollowRedirectsTrait.php
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
<?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\Internal;
|
||||
|
||||
use Symfony\Component\HttpClient\Response\AsyncContext;
|
||||
use Symfony\Component\HttpClient\Response\AsyncResponse;
|
||||
use Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
|
||||
/**
|
||||
* Follows redirections in userland so that decorators can inspect each hop.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait FollowRedirectsTrait
|
||||
{
|
||||
/**
|
||||
* @param string $url An already prepared, absolute URL
|
||||
* @param string $host The host name found in $url
|
||||
* @param \Closure(string $host, string $url, array &$options): void $onRedirect Called before each followed redirect
|
||||
*/
|
||||
private function followRedirects(string $method, string $url, string $host, array $options, \Closure $onRedirect): AsyncResponse
|
||||
{
|
||||
if (0 >= $maxRedirects = $options['max_redirects']) {
|
||||
return new AsyncResponse($this->client, $method, $url, $options);
|
||||
}
|
||||
|
||||
$options['max_redirects'] = 0;
|
||||
$redirectHeaders = [
|
||||
'host' => $host,
|
||||
'port' => parse_url($url, \PHP_URL_PORT),
|
||||
'with_auth' => $options['headers'],
|
||||
'no_auth' => $options['headers'],
|
||||
];
|
||||
|
||||
if (isset($options['normalized_headers']['host']) || isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) {
|
||||
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static fn ($h) => 0 !== stripos($h, 'Host:') && 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:'));
|
||||
}
|
||||
|
||||
return new AsyncResponse($this->client, $method, $url, $options, static function (ChunkInterface $chunk, AsyncContext $context) use (&$method, &$options, $maxRedirects, &$redirectHeaders, $onRedirect): \Generator {
|
||||
if (null !== $chunk->getError() || $chunk->isTimeout() || !$chunk->isFirst()) {
|
||||
yield $chunk;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$statusCode = $context->getStatusCode();
|
||||
|
||||
if ($statusCode < 300 || 400 <= $statusCode || null === $url = $context->getInfo('redirect_url')) {
|
||||
$context->passthru();
|
||||
|
||||
yield $chunk;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$host = parse_url($url, \PHP_URL_HOST);
|
||||
$onRedirect($host, $url, $options);
|
||||
|
||||
// Do like curl and browsers: turn POST to GET on 301, 302 and 303
|
||||
if (303 === $statusCode || 'POST' === $method && \in_array($statusCode, [301, 302], true)) {
|
||||
$method = 'HEAD' === $method ? 'HEAD' : 'GET';
|
||||
unset($options['body'], $options['json']);
|
||||
|
||||
if (isset($options['normalized_headers']['content-length']) || isset($options['normalized_headers']['content-type']) || isset($options['normalized_headers']['transfer-encoding'])) {
|
||||
$filterContentHeaders = static fn ($h) => 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
|
||||
$options['headers'] = array_filter($options['headers'], $filterContentHeaders);
|
||||
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);
|
||||
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
// Authorization and Cookie headers MUST NOT follow except for the initial host name
|
||||
$port = parse_url($url, \PHP_URL_PORT);
|
||||
$options['headers'] = $redirectHeaders['host'] === $host && ($redirectHeaders['port'] ?? null) === $port ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
|
||||
|
||||
static $redirectCount = 0;
|
||||
$context->setInfo('redirect_count', ++$redirectCount);
|
||||
|
||||
$context->replaceRequest($method, $url, $options);
|
||||
|
||||
if ($redirectCount >= $maxRedirects) {
|
||||
$context->passthru();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
152
vendor/symfony/http-client/Internal/HttplugWaitLoop.php
vendored
Normal file
152
vendor/symfony/http-client/Internal/HttplugWaitLoop.php
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
<?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\Internal;
|
||||
|
||||
use Http\Client\Exception\NetworkException;
|
||||
use Http\Promise\Promise;
|
||||
use Psr\Http\Message\RequestInterface as Psr7RequestInterface;
|
||||
use Psr\Http\Message\ResponseFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
|
||||
use Psr\Http\Message\StreamFactoryInterface;
|
||||
use Symfony\Component\HttpClient\Response\StreamableInterface;
|
||||
use Symfony\Component\HttpClient\Response\StreamWrapper;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class HttplugWaitLoop
|
||||
{
|
||||
/**
|
||||
* @param \SplObjectStorage<ResponseInterface, array{Psr7RequestInterface, Promise}>|null $promisePool
|
||||
*/
|
||||
public function __construct(
|
||||
private HttpClientInterface $client,
|
||||
private ?\SplObjectStorage $promisePool,
|
||||
private ResponseFactoryInterface $responseFactory,
|
||||
private StreamFactoryInterface $streamFactory,
|
||||
) {
|
||||
}
|
||||
|
||||
public function wait(?ResponseInterface $pendingResponse, ?float $maxDuration = null, ?float $idleTimeout = null): int
|
||||
{
|
||||
if (!$this->promisePool) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$guzzleQueue = \GuzzleHttp\Promise\Utils::queue();
|
||||
|
||||
if (0.0 === $remainingDuration = $maxDuration) {
|
||||
$idleTimeout = 0.0;
|
||||
} elseif (null !== $maxDuration) {
|
||||
$startTime = hrtime(true) / 1E9;
|
||||
$idleTimeout = max(0.0, min($maxDuration / 5, $idleTimeout ?? $maxDuration));
|
||||
}
|
||||
|
||||
do {
|
||||
foreach ($this->client->stream($this->promisePool, $idleTimeout) as $response => $chunk) {
|
||||
try {
|
||||
if (null !== $maxDuration && $chunk->isTimeout()) {
|
||||
goto check_duration;
|
||||
}
|
||||
|
||||
if ($chunk->isFirst()) {
|
||||
// Deactivate throwing on 3/4/5xx
|
||||
$response->getStatusCode();
|
||||
}
|
||||
|
||||
if (!$chunk->isLast()) {
|
||||
goto check_duration;
|
||||
}
|
||||
|
||||
if ([, $promise] = $this->promisePool[$response] ?? null) {
|
||||
unset($this->promisePool[$response]);
|
||||
$promise->resolve(self::createPsr7Response($this->responseFactory, $this->streamFactory, $this->client, $response, true));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if ([$request, $promise] = $this->promisePool[$response] ?? null) {
|
||||
unset($this->promisePool[$response]);
|
||||
|
||||
if ($e instanceof TransportExceptionInterface) {
|
||||
$e = new NetworkException($e->getMessage(), $request, $e);
|
||||
}
|
||||
|
||||
$promise->reject($e);
|
||||
}
|
||||
}
|
||||
|
||||
$guzzleQueue->run();
|
||||
|
||||
if ($pendingResponse === $response) {
|
||||
return $this->promisePool->count();
|
||||
}
|
||||
|
||||
check_duration:
|
||||
if (null !== $maxDuration && $idleTimeout && $idleTimeout > $remainingDuration = max(0.0, $maxDuration - hrtime(true) / 1E9 + $startTime)) {
|
||||
$idleTimeout = $remainingDuration / 5;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$count = $this->promisePool->count()) {
|
||||
return 0;
|
||||
}
|
||||
} while (null === $maxDuration || 0 < $remainingDuration);
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
public static function createPsr7Response(ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory, HttpClientInterface $client, ResponseInterface $response, bool $buffer): Psr7ResponseInterface
|
||||
{
|
||||
$responseParameters = [$response->getStatusCode()];
|
||||
|
||||
foreach ($response->getInfo('response_headers') as $h) {
|
||||
if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? (?:\d\d\d) (.+)#', $h, $m)) {
|
||||
$responseParameters[1] = $m[1];
|
||||
}
|
||||
}
|
||||
|
||||
$psrResponse = $responseFactory->createResponse(...$responseParameters);
|
||||
|
||||
foreach ($response->getHeaders(false) as $name => $values) {
|
||||
foreach ($values as $value) {
|
||||
try {
|
||||
$psrResponse = $psrResponse->withAddedHeader($name, $value);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// ignore invalid header
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($response instanceof StreamableInterface) {
|
||||
$body = $streamFactory->createStreamFromResource($response->toStream(false));
|
||||
} elseif (!$buffer) {
|
||||
$body = $streamFactory->createStreamFromResource(StreamWrapper::createResource($response, $client));
|
||||
} else {
|
||||
$body = $streamFactory->createStream($response->getContent(false));
|
||||
}
|
||||
|
||||
if ($body->isSeekable()) {
|
||||
try {
|
||||
$body->seek(0);
|
||||
} catch (\RuntimeException) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return $psrResponse->withBody($body);
|
||||
}
|
||||
}
|
||||
43
vendor/symfony/http-client/Internal/NativeClientState.php
vendored
Normal file
43
vendor/symfony/http-client/Internal/NativeClientState.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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\Internal;
|
||||
|
||||
/**
|
||||
* Internal representation of the native client's state.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class NativeClientState extends ClientState
|
||||
{
|
||||
public int $id;
|
||||
public int $maxHostConnections = \PHP_INT_MAX;
|
||||
public int $responseCount = 0;
|
||||
/** @var string[] */
|
||||
public array $dnsCache = [];
|
||||
public bool $sleep = false;
|
||||
/** @var int[] */
|
||||
public array $hosts = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = random_int(\PHP_INT_MIN, \PHP_INT_MAX);
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->responseCount = 0;
|
||||
$this->dnsCache = [];
|
||||
$this->hosts = [];
|
||||
}
|
||||
}
|
||||
32
vendor/symfony/http-client/Internal/PushedResponse.php
vendored
Normal file
32
vendor/symfony/http-client/Internal/PushedResponse.php
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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\Internal;
|
||||
|
||||
use Symfony\Component\HttpClient\Response\CurlResponse;
|
||||
|
||||
/**
|
||||
* A pushed response with its request headers.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class PushedResponse
|
||||
{
|
||||
public function __construct(
|
||||
public CurlResponse $response,
|
||||
public array $requestHeaders,
|
||||
public array $parentOptions,
|
||||
public \CurlHandle $handle,
|
||||
) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user