-
-
Notifications
You must be signed in to change notification settings - Fork 895
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(symfony): agnostic cache purger + souin support (#5273)
* feat: add Souin as a new http_cache provider * feat(symfony): agnostic cache purger Co-authored-by: darkweak <[email protected]>
- Loading branch information
Showing
11 changed files
with
427 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the API Platform project. | ||
* | ||
* (c) Kévin Dunglas <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ApiPlatform\HttpCache; | ||
|
||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
|
||
/** | ||
* Purges Souin. | ||
* | ||
* @author Sylvain Combraque <[email protected]> | ||
*/ | ||
class SouinPurger extends SurrogateKeysPurger | ||
{ | ||
private const MAX_HEADER_SIZE_PER_BATCH = 1500; | ||
private const SEPARATOR = ', '; | ||
private const HEADER = 'Surrogate-Key'; | ||
|
||
/** | ||
* @param HttpClientInterface[] $clients | ||
*/ | ||
public function __construct(array $clients) | ||
{ | ||
parent::__construct($clients, self::MAX_HEADER_SIZE_PER_BATCH, self::HEADER, self::SEPARATOR); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the API Platform project. | ||
* | ||
* (c) Kévin Dunglas <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ApiPlatform\HttpCache; | ||
|
||
use ApiPlatform\Exception\RuntimeException; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
|
||
/** | ||
* Surrogate keys purger. | ||
* | ||
* @author Sylvain Combraque <[email protected]> | ||
*/ | ||
class SurrogateKeysPurger implements PurgerInterface | ||
{ | ||
private const MAX_HEADER_SIZE_PER_BATCH = 1500; | ||
private const SEPARATOR = ', '; | ||
private const HEADER = 'Surrogate-Key'; | ||
|
||
/** | ||
* @param HttpClientInterface[] $clients | ||
*/ | ||
public function __construct(protected readonly array $clients, protected readonly int $maxHeaderLength = self::MAX_HEADER_SIZE_PER_BATCH, protected readonly string $header = self::HEADER, protected readonly string $separator = self::SEPARATOR) | ||
{ | ||
} | ||
|
||
/** | ||
* @return \Iterator<string> | ||
*/ | ||
private function getChunkedIris(array $iris): \Iterator | ||
{ | ||
if (!$iris) { | ||
return; | ||
} | ||
|
||
$chunk = array_shift($iris); | ||
foreach ($iris as $iri) { | ||
$nextChunk = sprintf('%s%s%s', $chunk, $this->separator, $iri); | ||
if (\strlen($nextChunk) <= $this->maxHeaderLength) { | ||
$chunk = $nextChunk; | ||
continue; | ||
} | ||
|
||
yield $chunk; | ||
$chunk = $iri; | ||
} | ||
|
||
yield $chunk; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function purge(array $iris): void | ||
{ | ||
foreach ($this->getChunkedIris($iris) as $chunk) { | ||
if (\strlen((string) $chunk) > $this->maxHeaderLength) { | ||
throw new RuntimeException(sprintf('IRI "%s" is too long to fit current max header length (currently set to "%s"). You can increase it using the "api_platform.http_cache.invalidation.max_header_length" parameter.', $chunk, $this->maxHeaderLength)); | ||
} | ||
|
||
foreach ($this->clients as $client) { | ||
$client->request( | ||
Request::METHOD_PURGE, | ||
'', | ||
['headers' => [$this->header => $chunk]] | ||
); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getResponseHeaders(array $iris): array | ||
{ | ||
return [$this->header => implode($this->separator, $iris)]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,86 +20,16 @@ | |
* | ||
* @author Kévin Dunglas <[email protected]> | ||
*/ | ||
final class VarnishXKeyPurger implements PurgerInterface | ||
final class VarnishXKeyPurger extends SurrogateKeysPurger | ||
{ | ||
private const VARNISH_MAX_HEADER_LENGTH = 8000; | ||
private const VARNISH_SEPARATOR = ' '; | ||
|
||
/** | ||
* @param HttpClientInterface[] $clients | ||
*/ | ||
public function __construct(private readonly array $clients, private readonly int $maxHeaderLength = self::VARNISH_MAX_HEADER_LENGTH, private readonly string $xkeyGlue = ' ') | ||
public function __construct(array $clients, int $maxHeaderLength = self::VARNISH_MAX_HEADER_LENGTH, string $xkeyGlue = self::VARNISH_SEPARATOR) | ||
{ | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function purge(array $iris): void | ||
{ | ||
if (!$iris) { | ||
return; | ||
} | ||
|
||
$irisChunks = array_chunk($iris, \count($iris)); | ||
|
||
foreach ($irisChunks as $irisChunk) { | ||
$this->purgeIris($irisChunk); | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getResponseHeaders(array $iris): array | ||
{ | ||
return ['xkey' => implode($this->xkeyGlue, $iris)]; | ||
} | ||
|
||
private function purgeIris(array $iris): void | ||
{ | ||
foreach ($this->chunkKeys($iris) as $keys) { | ||
$this->purgeKeys($keys); | ||
} | ||
} | ||
|
||
private function purgeKeys(string $keys): void | ||
{ | ||
foreach ($this->clients as $client) { | ||
$client->request('PURGE', '', ['headers' => ['xkey' => $keys]]); | ||
} | ||
} | ||
|
||
private function chunkKeys(array $keys): iterable | ||
{ | ||
$concatenatedKeys = implode($this->xkeyGlue, $keys); | ||
|
||
// If all keys fit in the header, we can return them | ||
if (\strlen($concatenatedKeys) <= $this->maxHeaderLength) { | ||
yield $concatenatedKeys; | ||
|
||
return; | ||
} | ||
|
||
$currentHeader = ''; | ||
|
||
foreach ($keys as $position => $key) { | ||
if (\strlen((string) $key) > $this->maxHeaderLength) { | ||
throw new \Exception(sprintf('IRI "%s" is too long to fit current max header length (currently set to "%s"). You can increase it using the "api_platform.http_cache.invalidation.max_header_length" parameter.', $key, $this->maxHeaderLength)); | ||
} | ||
|
||
$headerCandidate = sprintf('%s%s%s', $currentHeader, $position > 0 ? $this->xkeyGlue : '', $key); | ||
|
||
if (\strlen($headerCandidate) > $this->maxHeaderLength) { | ||
$nextKeys = \array_slice($keys, $position, \count($keys) - $position); | ||
|
||
yield $currentHeader; | ||
yield from $this->chunkKeys($nextKeys); | ||
|
||
break; | ||
} | ||
|
||
// Key can be added to header | ||
$currentHeader .= sprintf('%s%s', $position > 0 ? $this->xkeyGlue : '', $key); | ||
} | ||
parent::__construct($clients, $maxHeaderLength, 'xkey', $xkeyGlue); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.