-
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
[RFC] Added MaxHeaderValueLengthFormatter #424
Conversation
… formatter to a given 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.
i think the plan makes sense. i would be more relaxed to merge this if we can build a functional test with all caches that support tagging to prove that multiple tag headers will indeed work as expected (e.g. invalidation happens both if tag is in the first or in the second header)
i am a bit worried about the recursive splitting function you implemented. we potentially build several headers without then using them if they are too long. i think i would prefer to change the csv implementation to do that.
we already have a different solution to limit the header size when sending invalidation requests in the proxy clients:
FOSHttpCache/src/ProxyClient/Varnish.php
Lines 82 to 90 in 508d232
$chunkSize = $this->determineTagsPerHeader($tags, $banMode ? '|' : ' '); | |
foreach (array_chunk($tags, $chunkSize) as $tagchunk) { | |
if ($banMode) { | |
$this->invalidateByBan($tagchunk); | |
} else { | |
$this->invalidateByPurgekeys($tagchunk); | |
} | |
} |
FOSHttpCache/src/ProxyClient/Symfony.php
Lines 82 to 91 in 508d232
$chunkSize = $this->determineTagsPerHeader($escapedTags, ','); | |
foreach (array_chunk($escapedTags, $chunkSize) as $tagchunk) { | |
$this->queueRequest( | |
$this->options['tags_method'], | |
'/', | |
[$this->options['tags_header'] => implode(',', $tagchunk)], | |
false | |
); | |
} |
[3, ['foo', 'bar', 'baz'], ['foo', 'bar', 'baz']], | ||
[6, ['foo', 'bar', 'baz'], ['foo', 'bar', 'baz']], // with the , that equals 7 | ||
[7, ['foo', 'bar', 'baz'], ['foo,bar', 'baz']], | ||
[50, ['foo', 'bar', 'baz'], 'foo,bar,baz'], // long enough, must not be an array |
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.
why is it an array for 7 but not for 8+? that seems odd to me
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.
Max length 6
is too short for foo,bar
(7 chars) so it must result in two header values and 7 must result in one.
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.
sorry, i missed the the line with 7 still has 2 elements. all fine with that then.
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, this is because of array_merge(...$newValues);
. You could also call array_merge()
in every loop but it's a performance gain if you only merge it once :)
As far as I can see, no proxy currently tests cache tag invalidation. I could only help with the SF proxy test anyway where I would need to add a dependency to my alternative PSR-6 store.
I don't think this makes sense. The way I built it, the |
okay, agreed that the header should usually not need to be split many times. and indeed, we miss functional tests for tagging. we do have purge/refresh/ban in can you please add something to the documentation for how to use this? and do you plan on adding this to the bundle afterwards? |
16801a4
to
897ca10
Compare
Pheeew, that was a hard nut to crack 😄 But I've added functional tests for cache tag invalidation in case of Symfony now and covered both cases, single and multiple headers. Of course that only works if my PSR-6 cache store is installed so I'm not sure if everything is going to work out fine on travis yet.
Will do, once the PR is in ready-to-merge-state so we don't document stuff that's not true 😄
Yes I do. |
@dbu maybe you can help with the failing tests? |
src/ProxyClient/Symfony.php
Outdated
@@ -58,11 +58,13 @@ protected function configureOptions() | |||
'purge_method' => PurgeListener::DEFAULT_PURGE_METHOD, | |||
'tags_method' => PurgeTagsListener::DEFAULT_TAGS_METHOD, | |||
'tags_header' => PurgeTagsListener::DEFAULT_TAGS_HEADER, | |||
'tags_path' => '/', |
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.
i agree with the feature, but lets call this tags_invalidate_path. its for the other thing than tags_header. (yes, we should have called tags_method tags_invalidate_method)
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.
I agree here, changed in e6bfbf2.
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.
great job! i restarted some things and now only the 2 builds with symfony 2.3 fail. that smelled like some sort of code rot, so i restarted the master build... but that build is still green. could it be that something changed with support for same-name headers since version 2 of symfony? if there is indeed a difference, i suggest we jump to support only symfony 3|4 in version 2.5 of the library. 2.7 is pretty much eol and does not even receive regular bug fixes anymore...
could you check if that is indeed the case and if so bump the branch alias in composer to 2.5 and change the minimum symfony requirement, and adjust .travis.yml?
I've quickly tried to check but I would need to set up a docker container to replicate php 5.6 which is EOL very, very soon anyway too. Tbh, I would like to invest my time in more productive things than keeping support for versions nobody should be using anyway. If anybody really needs it, I think it should be them investing the time, not us 😄 Thus, I've bumped the minimum required versions. Looks like this is going to make it so I'll be adding some docs soon. |
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.
cool! one change to composer.json, otherwise i think this is now ready. and the functional test indeed did reveal an issue, even if only with old symfony versions...
can you now add the documentation explaining the header splitting, and also a documentation entry for the tags_invalidate_path option?
@andrerom afaik ez was using an older symfony version. do you see a problem with this change? we can keep doing some bugfixing in the 2.4 version if needed, but i'd rather move to new versions as its an overhead to support several versions, but its also quite some overhead to support very old symfony versions...
composer.json
Outdated
"php-http/client-implementation": "^1.0.0", | ||
"php-http/client-common": "^1.1.0", | ||
"php-http/message": "^1.0.0", | ||
"php-http/discovery": "^1.0" | ||
"php-http/discovery": "^1.0", | ||
"toflar/psr6-symfony-http-cache-store": "^1.1.2" |
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.
this line should not be here. better mark it in the conflicts section with < 1.1.2
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.
Ah that was accidentally, fixed, sorry.
.travis.yml
Outdated
- php: 7.2 | ||
env: DEPENDENCIES="symfony/lts:^2" | ||
- php: 7.2 | ||
env: DEPENDENCIES="symfony/lts:^3 toflar/psr6-symfony-http-cache-store:^1.0" |
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.
should we not keep this build with lts 3?
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.
As we've bumped to 3.4 as a minimum requirement I thought --prefer-lowest
would already cover that as "lowest" === "3.4" now. Wrong?
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.
i'd keep both so that we know if its only the oldest symfony 3.4 that fails or also the newest 3.4. (and also the other way round, we did have some regressions in the past, where some change in a patch version of old symfony broke stuff)
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.
Okay, I'll bring it back then :)
the php 5.6 build with symfony 3.4 seems to work. agree that we should not waste too much time on supporting outdated things. people stuck on legacy can still use the 2.4 tag. i guess FOSHttpCache 3.0 should be PHP ^7.1 . (not in a hurry to do that, though) |
Docs ready for review. |
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.
doc looks good, suggested some tweaks.
can you please add an entry to the changelog, mentioning that the splitting is not active by default?
@@ -7,7 +7,7 @@ cache: | |||
env: | |||
global: | |||
- VARNISH_VERSION=5.1 | |||
- DEPENDENCIES="toflar/psr6-symfony-http-cache-store:^1.0" | |||
- DEPENDENCIES="toflar/psr6-symfony-http-cache-store:^1.1.2" |
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.
doc/symfony-cache-configuration.rst
Outdated
@@ -226,6 +226,10 @@ configure the HTTP method and header used for tag purging: | |||
|
|||
**default**: ``X-Cache-Tags`` | |||
|
|||
* **tags_invalidate_path**: Path to where the invalidation request with the headers should be sent to. |
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.
i'd adjust this to what we have for tags_method
. maybe:
"Path on the caching proxy to which the purge tags request should be sent."
@@ -56,6 +56,23 @@ empty tags:: | |||
|
|||
$responseTagger = new ResponseTagger(['strict' => true]); | |||
|
|||
|
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.
i'd like to add a sub heading here to separate this a bit. "Working with large numbers of tags"?
doc/response-tagging.rst
Outdated
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. Of course, your proxy then has to support multiple |
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.
i would take this out of the paragraph and make a .. note::
below the code example. and then mention that symfony httpcache and varnish do support multiple headers.
Docs updated. |
can you please add the changelog entry and make the part about the proxy having to support multiple headers a the lowest version build failed, i restarted it to see if its a fluke. i wonder if those tests first check that we have a 2xx response first, or whether the real problem is some error from the server... we should probably output the response body when the assertion fails, to help debugging. |
f79faf2
to
f455450
Compare
f455450
to
7be255b
Compare
hah, now we see more. so on php 5.6 the toflar store is not found. which seems odd - do we not add it to the project? the other tests have a problem with mockery and the getHeaders method... |
|
||
$loader = require_once __DIR__.'/../../../../vendor/autoload.php'; | ||
|
||
$symfonyProxy = new SymfonyProxy(); | ||
|
||
$kernel = new AppKernel(); | ||
$kernel = new AppCache($kernel, new Store($symfonyProxy->getCacheDir()), null, ['debug' => true]); | ||
$kernel = new AppCache($kernel, new Psr6Store(['cache_directory' => $symfonyProxy->getCacheDir(), 'cache_tags_header' => 'X-Cache-Tags']), null, ['debug' => true]); |
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.
the lowest version build clears the dependencies. i think we should only try to use the Psr6 store if class_exists and otherwise use the normal store.
We are still on 1.x, started looking into moving to 2.x now that xkey is in, but it's invalidates most of our existing code so won't happen before later this fall, and that would be on our v2 (on PHP7+). Aka no blockers from us ;) |
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.
great job!
Almost there, finishing the last issues with the tests :) |
yep. why is the test for purge talking about the tags? do we have a wrong dependency here? |
No, no. I forgot half of it, I'm on it :) |
Wohooo 🎉 |
cheers! do you want to work on the bundle integration before i tag the release? could be a nice further sanity check ;-) |
I will be but I'm on holidays for 3 weeks now so that'll take a while 😄 So no need to tag that right now. Thanks! |
alright! enjoy your holidays then! |
Description ----------- This needs to hold on for the releases. Related PR's: Library: * FriendsOfSymfony/FOSHttpCache#424 * FriendsOfSymfony/FOSHttpCache#426 Bundle: * FriendsOfSymfony/FOSHttpCache#426 Commits ------- f782ecc Configured max header value instead of removing the header altogether 1461dd0 Use 4096 instead of 2048
Description ----------- This needs to hold on for the releases. Related PR's: Library: * FriendsOfSymfony/FOSHttpCache#424 * FriendsOfSymfony/FOSHttpCache#426 Bundle: * FriendsOfSymfony/FOSHttpCache#426 Commits ------- f782ecc9 Configured max header value instead of removing the header altogether 1461dd09 Use 4096 instead of 2048
This is an idea on how we could try to solve the maximum length of the header values in the
ResponseTagger
. We already haveTagHeaderFormatters
so I thought I'll write one that takes an inner formatter and splits the tags of this one to make sure it does not exceed the max length. This would allow to use this formatter to limit the header value length of any other formatter (the built in CSV or your very own) very easily (a pretty cool concept I find 😄 ).For this to work, however, I had to adjust the return value of the
TagHeaderFormatter
interface and I know that in theory this is a BC break. BUT: I wanted to discuss this here because it's really only a theoretical BC break. So what I did is I changed this:to this:
So basically, it now allows an array of strings to be returned. This means all the existing formatters that return a string are just fine. Only the ones calling the method must also deal with an array return value. And this in fact should've always been the case because both, the PSR-7
Response::withHeader()
as well as the Symfony$response->headers->set()
accept an array to support multi-value headers. So, in theory this is a BC break but I don't think anyone will be affected.Wdyt?