diff --git a/Resources/doc/reference/configuration/tags.rst b/Resources/doc/reference/configuration/tags.rst index fdf304fc..eba10411 100644 --- a/Resources/doc/reference/configuration/tags.rst +++ b/Resources/doc/reference/configuration/tags.rst @@ -49,6 +49,32 @@ section of the tag configuration and ``@tag`` :ref:`annotations`. tags: expression_language: app.expression_language +``max_header_value_length`` +--------------------- + +**type**: ``integer`` **default**: ``null`` + +By default, the generated response header will not be split into multiple headers. +This means that depending on the amount of tags generated in your application the +value of that header might become pretty long. This again might cause issues with +your webserver which usually come with a pre-defined maximum header value length and +will throw an exception if you exceed this. Using this configuration key you can +configure a maximum length **in bytes** which will split your value into multiple +headers. Note that you might update your proxy configuration because it needs +to be able to handle multiple headers instead of just one. + +.. code-block:: yaml + + # app/config/config.yml + fos_http_cache: + tags: + max_header_value_length: 4096 + +.. note:: + + 4096 bytes is generally a good choice because it seems like most webservers have + a maximum value of 4 KB configured. + ``strict`` ---------- diff --git a/composer.json b/composer.json index d5071a08..bbeed824 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ ], "require": { "php": "^7.1", - "friendsofsymfony/http-cache": "^2.3", + "friendsofsymfony/http-cache": "^2.5", "symfony/framework-bundle": "^2.8.18||^3.3||^4.0", "symfony/http-foundation": "^2.8.18.13||^3.3||^4.0" }, diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 0fa7fdf5..44104022 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -655,6 +655,10 @@ private function addTagSection(ArrayNodeDefinition $rootNode) ->defaultNull() ->info('Character(s) to use to separate multiple tags. Defaults to " " for Varnish xkey or "," otherwise') ->end() + ->scalarNode('max_header_value_length') + ->defaultNull() + ->info('If configured the tag header value will be split into multiple response headers of the same name (see "response_header" configuration key) that all do not exceed the configured "max_header_value_length" (recommended is 4KB = 4096) - configure in bytes.') + ->end() ->arrayNode('rules') ->prototype('array') ->fixXmlConfig('tag') diff --git a/src/DependencyInjection/FOSHttpCacheExtension.php b/src/DependencyInjection/FOSHttpCacheExtension.php index cfb61974..3f892511 100644 --- a/src/DependencyInjection/FOSHttpCacheExtension.php +++ b/src/DependencyInjection/FOSHttpCacheExtension.php @@ -15,6 +15,7 @@ use FOS\HttpCache\ProxyClient\ProxyClient; use FOS\HttpCache\ProxyClient\Varnish; use FOS\HttpCache\SymfonyCache\KernelDispatcher; +use FOS\HttpCache\TagHeaderFormatter\MaxHeaderValueLengthFormatter; use FOS\HttpCacheBundle\DependencyInjection\Compiler\HashGeneratorPass; use FOS\HttpCacheBundle\Http\ResponseMatcher\ExpressionResponseMatcher; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; @@ -492,6 +493,13 @@ private function loadCacheTagging(ContainerBuilder $container, XmlFileLoader $lo if (!empty($config['rules'])) { $this->loadTagRules($container, $config['rules']); } + + if (null !== $config['max_header_value_length']) { + $container->register('fos_http_cache.tag_handler.max_header_value_length_header_formatter', MaxHeaderValueLengthFormatter::class) + ->setDecoratedService('fos_http_cache.tag_handler.header_formatter') + ->addArgument(new Reference('fos_http_cache.tag_handler.max_header_value_length_header_formatter.inner')) + ->addArgument((int) $config['max_header_value_length']); + } } private function loadTest(ContainerBuilder $container, XmlFileLoader $loader, array $config) diff --git a/tests/Unit/DependencyInjection/ConfigurationTest.php b/tests/Unit/DependencyInjection/ConfigurationTest.php index 4c33ea56..fc622112 100644 --- a/tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/tests/Unit/DependencyInjection/ConfigurationTest.php @@ -132,6 +132,7 @@ public function testSupportsAllConfigFormats() ], ], 'separator' => ',', + 'max_header_value_length' => null, ], 'invalidation' => [ 'enabled' => 'auto', @@ -689,6 +690,7 @@ private function getEmptyConfig() 'expression_language' => null, 'rules' => [], 'separator' => ',', + 'max_header_value_length' => null, ], 'invalidation' => [ 'enabled' => false, diff --git a/tests/Unit/DependencyInjection/FOSHttpCacheExtensionTest.php b/tests/Unit/DependencyInjection/FOSHttpCacheExtensionTest.php index 88c0024a..39354ee3 100644 --- a/tests/Unit/DependencyInjection/FOSHttpCacheExtensionTest.php +++ b/tests/Unit/DependencyInjection/FOSHttpCacheExtensionTest.php @@ -245,6 +245,27 @@ public function testConfigLoadTagRules() $this->assertRequestMatcherCreated($container, ['_controller' => '^AcmeBundle:Default:index$']); $this->assertListenerHasRule($container, 'fos_http_cache.event_listener.tag'); + $this->assertFalse($container->hasDefinition('fos_http_cache.tag_handler.max_header_value_length_header_formatter')); + } + + + public function testConfigWithMaxHeaderLengthValueDecoratesTagService() + { + $config = $this->getBaseConfig() + [ + 'tags' => [ + 'max_header_value_length' => 2048, + ], + ]; + + $container = $this->createContainer(); + $this->extension->load([$config], $container); + + $this->assertTrue($container->hasDefinition('fos_http_cache.tag_handler.max_header_value_length_header_formatter')); + $definition = $container->getDefinition('fos_http_cache.tag_handler.max_header_value_length_header_formatter'); + + $this->assertSame('fos_http_cache.tag_handler.header_formatter', $definition->getDecoratedService()[0]); + $this->assertSame('fos_http_cache.tag_handler.max_header_value_length_header_formatter.inner', (string) $definition->getArgument(0)); + $this->assertSame(2048, $definition->getArgument(1)); } public function testConfigLoadInvalidatorRules()