框架升级及修复升级后的bug

This commit is contained in:
Dev
2026-10-16 18:13:09 +08:00
parent dafb0c96c0
commit 2d342bcbd7
537 changed files with 49836 additions and 40933 deletions
+87 -35
View File
@@ -11,8 +11,6 @@
namespace Symfony\Component\HttpClient;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\Caching\Freshness;
use Symfony\Component\HttpClient\Chunk\ErrorChunk;
use Symfony\Component\HttpClient\Exception\ChunkCacheItemNotFoundException;
@@ -21,6 +19,9 @@ use Symfony\Component\HttpClient\Response\AsyncContext;
use Symfony\Component\HttpClient\Response\AsyncResponse;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Component\HttpClient\Response\ResponseStream;
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
use Symfony\Component\HttpKernel\HttpCache\StoreInterface;
use Symfony\Component\HttpKernel\HttpClientKernel;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
use Symfony\Contracts\HttpClient\ChunkInterface;
@@ -43,7 +44,7 @@ use Symfony\Contracts\Service\ResetInterface;
*
* @see https://www.rfc-editor.org/rfc/rfc9111
*/
class CachingHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface
class CachingHttpClient implements HttpClientInterface, ResetInterface
{
use AsyncDecoratorTrait {
stream as asyncStream;
@@ -100,27 +101,47 @@ class CachingHttpClient implements HttpClientInterface, LoggerAwareInterface, Re
*/
private const HEURISTIC_FRESHNESS_FRACTION = 0.1;
private TagAwareCacheInterface|HttpCache $cache;
private array $defaultOptions = self::OPTIONS_DEFAULTS;
private bool $isInnerRequest = false;
private ?LoggerInterface $logger = null;
/**
* @param bool $sharedCache Indicates whether this cache is shared or private. When true, responses
* may be skipped from caching in presence of certain headers
* (e.g. Authorization) unless explicitly marked as public.
* @param positive-int $maxTtl The maximum time-to-live (in seconds) for cached responses.
* If a server-provided TTL exceeds this value, it will be capped
* to this maximum.
* @param bool $sharedCache Indicates whether this cache is shared or private. When true, responses
* may be skipped from caching in presence of certain headers
* (e.g. Authorization) unless explicitly marked as public.
* @param int|null $maxTtl The maximum time-to-live (in seconds) for cached responses.
* If a server-provided TTL exceeds this value, it will be capped
* to this maximum.
*/
public function __construct(
private HttpClientInterface $client,
private readonly TagAwareCacheInterface $cache,
TagAwareCacheInterface|StoreInterface $cache,
array $defaultOptions = [],
private readonly bool $sharedCache = true,
private readonly ?int $maxTtl = 86400,
private readonly ?int $maxTtl = null,
) {
if (null === $maxTtl) {
trigger_deprecation('symfony/http-client', '8.1', 'Passing null as "$maxTtl" to "%s()" is deprecated, pass a positive integer instead.', __METHOD__);
if ($cache instanceof StoreInterface) {
trigger_deprecation('symfony/http-client', '7.4', 'Passing a "%s" as constructor\'s 2nd argument of "%s" is deprecated, "%s" expected.', StoreInterface::class, __CLASS__, TagAwareCacheInterface::class);
if (!class_exists(HttpClientKernel::class)) {
throw new \LogicException(\sprintf('Using "%s" requires the HttpKernel component, try running "composer require symfony/http-kernel".', __CLASS__));
}
$kernel = new HttpClientKernel($client);
$this->cache = new HttpCache($kernel, $cache, null, $defaultOptions);
unset($defaultOptions['debug']);
unset($defaultOptions['default_ttl']);
unset($defaultOptions['private_headers']);
unset($defaultOptions['skip_response_headers']);
unset($defaultOptions['allow_reload']);
unset($defaultOptions['allow_revalidate']);
unset($defaultOptions['stale_while_revalidate']);
unset($defaultOptions['stale_if_error']);
unset($defaultOptions['trace_level']);
unset($defaultOptions['trace_header']);
} else {
$this->cache = $cache;
}
if ($defaultOptions) {
@@ -128,17 +149,16 @@ class CachingHttpClient implements HttpClientInterface, LoggerAwareInterface, Re
}
}
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
public function request(string $method, string $url, array $options = []): ResponseInterface
{
if ($this->isInnerRequest) {
return $this->client->request($method, $url, $options);
}
if ($this->cache instanceof HttpCache) {
return $this->legacyRequest($method, $url, $options);
}
[$fullUrl, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions);
$fullUrl = implode('', $fullUrl);
@@ -225,8 +245,10 @@ class CachingHttpClient implements HttpClientInterface, LoggerAwareInterface, Re
return self::createGatewayTimeoutResponse($method, $url, $options);
}
$requestTime = time();
// consistent expiration time for all items
$expiresAt = \DateTimeImmutable::createFromFormat('U', time() + ($this->maxTtl ?? 86400));
$expiresAt = null === $this->maxTtl ? null : \DateTimeImmutable::createFromFormat('U', time() + $this->maxTtl);
$passthru = function (ChunkInterface $chunk, AsyncContext $context) use (
$expiresAt,
@@ -243,6 +265,7 @@ class CachingHttpClient implements HttpClientInterface, LoggerAwareInterface, Re
$hasClientConditionalValidator,
$sentCacheValidator,
$allowStaleFallback,
$requestTime,
): \Generator {
static $attemptTag = null;
static $firstChunkKey = null;
@@ -252,11 +275,6 @@ class CachingHttpClient implements HttpClientInterface, LoggerAwareInterface, Re
null !== $attemptTag && $this->cache->invalidateTags([$attemptTag]);
if ($allowStaleFallback && Freshness::StaleButUsable === $freshness) {
$this->logger?->info('Serving stale cached response for "{method} {url}" because the upstream call failed: {error}.', [
'method' => $method,
'url' => $url,
'error' => $chunk instanceof ErrorChunk ? $chunk->getError() : 'timeout',
]);
// avoid throwing exception in ErrorChunk#__destruct()
$chunk instanceof ErrorChunk && $chunk->didThrow(true);
$context->passthru();
@@ -298,10 +316,6 @@ class CachingHttpClient implements HttpClientInterface, LoggerAwareInterface, Re
}
$responseTime = time();
$requestTime = (int) ($context->getInfo('start_time') ?? $responseTime);
if ($requestTime <= $cachedData['stored_at']) {
$requestTime = $responseTime;
}
$correctedInitialAge = self::getCorrectedInitialAge($headers, $requestTime, $responseTime);
$updatedHeaders = self::filterStorableHeaders(array_merge($cachedData['headers'], $headers), self::getExcludedHeaders($headers));
$updatedCachedData = array_replace($cachedData, [
@@ -347,11 +361,6 @@ class CachingHttpClient implements HttpClientInterface, LoggerAwareInterface, Re
if ($statusCode >= 500 && $statusCode < 600) {
if ($allowStaleFallback && Freshness::StaleButUsable === $freshness) {
$this->logger?->info('Serving stale cached response for "{method} {url}" because the upstream returned a server error (HTTP {status}).', [
'method' => $method,
'url' => $url,
'status' => $statusCode,
]);
$context->passthru();
$context->replaceResponse($this->createResponseFromCache($cachedData, $method, $url, $options, $metadataKey));
@@ -425,7 +434,6 @@ class CachingHttpClient implements HttpClientInterface, LoggerAwareInterface, Re
];
}, \INF);
$requestTime = (int) ($context->getInfo('start_time') ?? time());
$responseTime = time();
$correctedInitialAge = self::getCorrectedInitialAge($headers, $requestTime, $responseTime);
$maxAge = $this->determineMaxAge($headers, self::parseCacheControlHeader($headers['cache-control'] ?? []), $correctedInitialAge, $requestTime, $responseTime);
@@ -515,6 +523,50 @@ class CachingHttpClient implements HttpClientInterface, LoggerAwareInterface, Re
})());
}
private function legacyRequest(string $method, string $url, array $options = []): ResponseInterface
{
[$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions, true);
$url = implode('', $url);
if (!empty($options['body']) || !empty($options['extra']['no_cache']) || !\in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
return new AsyncResponse($this->client, $method, $url, $options);
}
$request = OutgoingRequest::create($url, $method);
$request->attributes->set('http_client_options', $options);
foreach ($options['normalized_headers'] as $name => $values) {
if ('cookie' !== $name) {
$headerValues = [];
foreach ($values as $value) {
$headerValues[] = substr($value, 2 + \strlen($name));
}
$request->headers->set($name, $headerValues);
continue;
}
foreach ($values as $cookies) {
foreach (explode('; ', substr($cookies, \strlen('Cookie: '))) as $cookie) {
if ('' !== $cookie) {
$cookie = explode('=', $cookie, 2);
$request->cookies->set($cookie[0], $cookie[1] ?? '');
}
}
}
}
$response = $this->cache->handle($request);
$response = new MockResponse($response->getContent(), [
'http_code' => $response->getStatusCode(),
'response_headers' => $response->headers->allPreserveCase(),
]);
return MockResponse::fromRequest($method, $url, $options, $response);
}
private static function hash(string $toHash): string
{
return str_replace('/', '_', base64_encode(hash('sha256', $toHash, true)));