Skip to content

Commit

Permalink
Merge pull request api-platform#2865 from jocel1/varnish-purge-by-chunk
Browse files Browse the repository at this point in the history
send Varnish BAN requests with smaller headers
  • Loading branch information
teohhanhui authored Mar 10, 2020
2 parents 842ab30 + c6055be commit 3b8dc7d
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ private function registerCommonConfiguration(ContainerBuilder $container, array
$container->setParameter('api_platform.http_cache.shared_max_age', $config['http_cache']['shared_max_age']);
$container->setParameter('api_platform.http_cache.vary', $config['http_cache']['vary']);
$container->setParameter('api_platform.http_cache.public', $config['http_cache']['public']);
$container->setParameter('api_platform.http_cache.invalidation.max_header_length', $config['http_cache']['invalidation']['max_header_length']);

$container->setAlias('api_platform.operation_path_resolver.default', $config['default_operation_path_resolver']);
$container->setAlias('api_platform.path_segment_name_generator', $config['path_segment_name_generator']);
Expand Down Expand Up @@ -532,7 +533,8 @@ private function registerHttpCacheConfiguration(ContainerBuilder $container, arr
$definitions[] = $definition;
}

$container->getDefinition('api_platform.http_cache.purger.varnish')->addArgument($definitions);
$container->getDefinition('api_platform.http_cache.purger.varnish')->setArguments([$definitions,
$config['http_cache']['invalidation']['max_header_length'], ]);
$container->setAlias('api_platform.http_cache.purger', 'api_platform.http_cache.purger.varnish');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,10 @@ private function addHttpCacheSection(ArrayNodeDefinition $rootNode): void
->prototype('scalar')->end()
->info('URLs of the Varnish servers to purge using cache tags when a resource is updated.')
->end()
->integerNode('max_header_length')
->defaultValue(7500)
->info('Max header length supported by the server')
->end()
->variableNode('request_options')
->defaultValue([])
->validate()
Expand Down
40 changes: 39 additions & 1 deletion src/HttpCache/VarnishPurger.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,41 @@
final class VarnishPurger implements PurgerInterface
{
private $clients;
private $maxHeaderLength;

/**
* @param ClientInterface[] $clients
*/
public function __construct(array $clients)
public function __construct(array $clients, int $maxHeaderLength = 7500)
{
$this->clients = $clients;
$this->maxHeaderLength = $maxHeaderLength;
}

/**
* Calculate how many tags fit into the header.
*
* This assumes that the tags are separated by one character.
*
* From https://github.com/FriendsOfSymfony/FOSHttpCache/blob/2.8.0/src/ProxyClient/HttpProxyClient.php#L137
*
* @param string[] $escapedTags
* @param string $glue The concatenation string to use
*
* @return int Number of tags per tag invalidation request
*/
private function determineTagsPerHeader(array $escapedTags, string $glue): int
{
if (mb_strlen(implode($glue, $escapedTags)) < $this->maxHeaderLength) {
return \count($escapedTags);
}
/*
* estimate the amount of tags to invalidate by dividing the max
* header length by the largest tag (minus the glue length)
*/
$tagsize = max(array_map('mb_strlen', $escapedTags));

return (int) floor($this->maxHeaderLength / ($tagsize + \strlen($glue))) ?: 1;
}

/**
Expand All @@ -43,6 +71,16 @@ public function purge(array $iris)
return;
}

$chunkSize = $this->determineTagsPerHeader($iris, '|');

$irisChunks = array_chunk($iris, $chunkSize);
foreach ($irisChunks as $irisChunk) {
$this->purgeRequest($irisChunk);
}
}

private function purgeRequest(array $iris)
{
// Create the regex to purge all tags in just one request
$parts = array_map(function ($iri) {
return sprintf('(^|\,)%s($|\,)', preg_quote($iri));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,7 @@ private function getPartialContainerBuilderProphecy($configuration = null)
'api_platform.http_cache.shared_max_age' => null,
'api_platform.http_cache.vary' => ['Accept'],
'api_platform.http_cache.public' => null,
'api_platform.http_cache.invalidation.max_header_length' => 7500,
'api_platform.defaults' => ['attributes' => []],
'api_platform.enable_entrypoint' => true,
'api_platform.enable_docs' => true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm
'enabled' => false,
'varnish_urls' => [],
'request_options' => [],
'max_header_length' => 7500,
],
'etag' => true,
'max_age' => null,
Expand Down
11 changes: 11 additions & 0 deletions tests/HttpCache/VarnishPurgerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,20 @@ public function testPurge()
$clientProphecy2->request('BAN', '', ['headers' => ['ApiPlatform-Ban-Regex' => '(^|\,)/foo($|\,)']])->willReturn(new Response())->shouldBeCalled();
$clientProphecy2->request('BAN', '', ['headers' => ['ApiPlatform-Ban-Regex' => '((^|\,)/foo($|\,))|((^|\,)/bar($|\,))']])->willReturn(new Response())->shouldBeCalled();

$clientProphecy3 = $this->prophesize(ClientInterface::class);
$clientProphecy3->request('BAN', '', ['headers' => ['ApiPlatform-Ban-Regex' => '(^|\,)/foo($|\,)']])->willReturn(new Response())->shouldBeCalled();
$clientProphecy3->request('BAN', '', ['headers' => ['ApiPlatform-Ban-Regex' => '(^|\,)/bar($|\,)']])->willReturn(new Response())->shouldBeCalled();

$clientProphecy4 = $this->prophesize(ClientInterface::class);
$clientProphecy4->request('BAN', '', ['headers' => ['ApiPlatform-Ban-Regex' => '(^|\,)/foo($|\,)']])->willReturn(new Response())->shouldBeCalled();
$clientProphecy4->request('BAN', '', ['headers' => ['ApiPlatform-Ban-Regex' => '(^|\,)/bar($|\,)']])->willReturn(new Response())->shouldBeCalled();

$purger = new VarnishPurger([$clientProphecy1->reveal(), $clientProphecy2->reveal()]);
$purger->purge(['/foo']);
$purger->purge(['/foo' => '/foo', '/bar' => '/bar']);

$purger = new VarnishPurger([$clientProphecy3->reveal(), $clientProphecy4->reveal()], 5);
$purger->purge(['/foo' => '/foo', '/bar' => '/bar']);
}

public function testEmptyTags()
Expand Down

0 comments on commit 3b8dc7d

Please sign in to comment.