-
Notifications
You must be signed in to change notification settings - Fork 61
[RFC] Added MaxHeaderValueLengthFormatter #424
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f016240
bec93f3
897ca10
e6bfbf2
8cab719
bcc091f
7932ab1
da17114
6702045
8034bb6
d6c710d
c6022b4
f44d4ee
7be255b
8f38bd9
7cfe604
c04ae47
67eacbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,6 +56,35 @@ empty tags:: | |
|
||
$responseTagger = new ResponseTagger(['strict' => true]); | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'd like to add a sub heading here to separate this a bit. "Working with large numbers of tags"? |
||
Working with large numbers of tags | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Depending on how many tags your system usually generates your tags header value | ||
might get pretty long. In that case, again depending on your setup, you might run | ||
into server exceptions because the header value is too big to send. Mostly, this | ||
value seems to be about 4KB. The only thing you can do in such a case is to split | ||
up one header into multiple ones. | ||
|
||
.. note:: | ||
|
||
Of course, your proxy then has to support multiple header values otherwise | ||
you'll end up with a proxy that only reads the first line of your tags. | ||
|
||
This library ships with a ``MaxHeaderValueLengthFormatter`` that does | ||
the splitting for you. You give it an inner formatter and the maximum length like so:: | ||
|
||
|
||
use FOS\HttpCache\TagHeaderFormatter\CommaSeparatedTagHeaderFormatter; | ||
use FOS\HttpCache\TagHeaderFormatter\MaxHeaderValueLengthFormatter | ||
|
||
$inner = new CommaSeparatedTagHeaderFormatter('X-Cache-Tags', ','); | ||
$formatter new MaxHeaderValueLengthFormatter($inner, 4096); | ||
|
||
.. note:: | ||
|
||
Both, Varnish and Symfony HttpCache support multiple cache tag headers. | ||
|
||
Usage | ||
~~~~~ | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the FOSHttpCache package. | ||
* | ||
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace FOS\HttpCache\TagHeaderFormatter; | ||
|
||
use FOS\HttpCache\Exception\InvalidTagException; | ||
|
||
/** | ||
* A header formatter that splits the value(s) from a given | ||
* other header formatter (e.g. the CommaSeparatedTagHeaderFormatter) | ||
* into multiple headers making sure none of the header values | ||
* exceeds the configured limit. | ||
* | ||
* @author Yanick Witschi <[email protected]> | ||
*/ | ||
class MaxHeaderValueLengthFormatter implements TagHeaderFormatter | ||
{ | ||
/** | ||
* @var TagHeaderFormatter | ||
*/ | ||
private $inner; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
private $maxHeaderValueLength; | ||
|
||
/** | ||
* The default value of the maximum header length is 4096 because most | ||
* servers limit header values to 4kb. | ||
* HTTP messages cannot carry characters outside the ISO-8859-1 standard so they all | ||
* use up just one byte. | ||
* | ||
* @param TagHeaderFormatter $inner | ||
* @param int $maxHeaderValueLength | ||
*/ | ||
public function __construct(TagHeaderFormatter $inner, $maxHeaderValueLength = 4096) | ||
{ | ||
$this->inner = $inner; | ||
$this->maxHeaderValueLength = $maxHeaderValueLength; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getTagsHeaderName() | ||
{ | ||
$this->inner->getTagsHeaderName(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getTagsHeaderValue(array $tags) | ||
{ | ||
$values = (array) $this->inner->getTagsHeaderValue($tags); | ||
$newValues = [[]]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this not be a single There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, this is because of |
||
|
||
foreach ($values as $value) { | ||
if ($this->isHeaderTooLong($value)) { | ||
list($firstTags, $secondTags) = $this->splitTagsInHalves($tags); | ||
|
||
$newValues[] = (array) $this->getTagsHeaderValue($firstTags); | ||
$newValues[] = (array) $this->getTagsHeaderValue($secondTags); | ||
} else { | ||
$newValues[] = [$value]; | ||
} | ||
} | ||
|
||
$newValues = array_merge(...$newValues); | ||
|
||
if (1 === count($newValues)) { | ||
return $newValues[0]; | ||
} | ||
|
||
return $newValues; | ||
} | ||
|
||
/** | ||
* @param string $value | ||
* | ||
* @return bool | ||
*/ | ||
private function isHeaderTooLong($value) | ||
{ | ||
return mb_strlen($value) > $this->maxHeaderValueLength; | ||
} | ||
|
||
/** | ||
* Split an array of tags in two more or less equal sized arrays. | ||
* | ||
* @param array $tags | ||
* | ||
* @return array | ||
* | ||
* @throws InvalidTagException | ||
*/ | ||
private function splitTagsInHalves(array $tags) | ||
{ | ||
if (1 === count($tags)) { | ||
throw new InvalidTagException(sprintf( | ||
'You configured a maximum header length of %d but the tag "%s" is too long.', | ||
$this->maxHeaderValueLength, | ||
$tags[0] | ||
)); | ||
} | ||
|
||
$size = ceil(count($tags) / 2); | ||
|
||
return array_chunk($tags, $size); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should add a conflicts section to composer.json for older versions of the store, if the library does not work correctly with older versions. as this is an optional dependency, we can not control the version being installed otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.