框架升级及修复升级后的bug
This commit is contained in:
+87
-35
@@ -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)));
|
||||
|
||||
Reference in New Issue
Block a user