Problem/Motivation
When a page redirects, no cacheability metadata is attached to the response. As a result it is currently not safe to cache redirect responses.
At #2527126-126: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them, @Berdir pointed out that the redirect module in Drupal 8 in fact does force its redirect responses to be cached, by exploiting an implementation detail in Page Cache (that has been present for a long time in Drupal 8, and is also present in RC1): the Page Cache has been parsing the X-Drupal-Cache-Tags header to make its redirect responses be cached in the Page Cache.
That issue is changing that behavior to not rely on that internal implementation detail, and is making Page Cache only support tag-based invalidation if the response implements CacheableResponseInterface.
Berdir raised a concern in that issue that this is technically breaking BC (#2527126-128: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them. So Wim Leers brought back BC (#2527126-130: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them) despite his concerns that this would be far more difficult to understand.
effulgentsia then raised a concern (#2527126-134: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them) about that BC layer: providing that BC is effectively turning an implementation detail into an API. catch discussed this concern of effulgentsia with berdir in IRC (#2527126-136: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them) and confirms that this was not actually an API. In fact, he goes further: he says it's actually a bug that redirect's responses can be cached by exploiting that implementation detail. Hence it's a bug fix to stop caching any redirects (i.e. what #2527126: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them does), and an API addition to start caching some of them (i.e. THIS issue).
Finally, given that careful weighing of pros & cons, alexpott & catch concluded (#2527126-138: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them) that THIS issue should go in first, so that redirect (and other modules) in D8 contrib can send cacheable redirect responses using the official API, and THEN we should do #2527126: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them.
(If anything is unclear, please read #2527126-136: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them in full, it contains a lengthier explanation with more nuance.)
Proposed resolution
- Introduce
CacheableRedirectResponse. - Using that in
RedirectLeadingSlashesSubscriber. - Introduce
CacheableSecuredRedirectResponsewhich extendSecuredRedirectResponse, making it cacheable. We can't updateSecuredRedirectResponsedirectly because it lives inComponentandCacheableResponseInterfacelives inCore. - Add test coverage for that in
TrustedRedirectResponseTest.
Only 7 files touched, only 8 KB patch, no disruption, no BC break, and very simple to review :)
Remaining tasks
None.
User interface changes
None.
API changes
Beta phase evaluation
| Issue priority | Major because hugely affects front-end performance. |
|---|---|
| Prioritized changes | The main goal of this issue is (front-end) performance: This would be very valuable to have in 8.0.0, especially for modules that do redirects. Cacheable redirects make things much faster for the end user (because it also means the redirect can be cached in the browser's cache). It's also a contributed project blocker. |
| Disruption | Zero disruption. |
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #62 | cacheableredirectresponse-2573923-62.patch | 8.05 KB | wim leers |
Comments
Comment #2
znerol commentedComment #3
znerol commentedThis might be wrong. We might want to collect cacheability metadata for the current request, not (only) for the route we are about to redirect to.
Comment #4
znerol commentedThe remaining instances of
RedirectResponseare either used in an administrative context or in tests:Comment #9
znerol commentedTest failures are reproducible with the following test:
The second time
drupalLogout()is called, the dynamic cache produces aHITand thus the logout is not performed.Comment #10
znerol commentedComment #11
znerol commented@WimLeers suggests to just use the
no_cacheroute option.Comment #16
wim leersPatch looks great :)
Comment #17
znerol commentedComment #19
znerol commentedUsing
no_cacherouting option in order to excludeLocaleController::checkTranslation(), also introduce aCacheableRedirectResponseInterfacefor proper type hinting.Comment #20
znerol commentedReally not sure what should be done about
TrustedRedirectResponseandLocalRedirectResponse.Comment #22
znerol commentedCompletely missed that Symfony does not provide a
RedirectResponseInterface:(Adding
CacheableResponseTraittoTrustedRedirectResponseandLocalRedirectResponse. It looks like contrib will benefit from that.Comment #23
znerol commentedComment #24
znerol commentedComment #28
znerol commentedThe
url()call in thelocale_test.moduleis spoiling the render context. Let's use something more appropriate (i.e.file_create_url).Comment #32
fabianx commentedLooks great to me; boldly RTBCing.
Comment #34
borisson_Testbot--
Comment #36
fabianx commentedComment #39
wim leersComment #40
wim leersThis would be very valuable to have in 8.0.0, especially for modules that do redirects. Cacheable redirects make things much faster for the end user (because it also means the redirect can be cached in the browser's cache).
Comment #41
xjmSee https://groups.drupal.org/node/484788 for more information on the rc target tag. Could we put a bit on the impact vs. disruption in the summary to help decide when this patch can go in safely? Thanks! Maybe @catch could confirm if this is non-disruptive and important enough to be committed during RC.
Comment #42
wim leersI first thought these mean this can't go in after RC. But I think that's not true. Even if you've subclassed these objects, you won't have to change anything. So AFAICT there's no API change.
So I was mistaken in tagging this earlier in any case. Sorry about that.
Added the impact vs disruption to the IS.
Comment #43
fabianx commentedRTBC + 1 - it would be good to get this in before RC, but it potentially has big enough merit and less enough disruption to justify to go in later.
Comment #45
fabianx commentedComment #46
visabhishek commentedRe-roll
Comment #47
visabhishek commentedComment #49
wim leersRedoing the reroll, because #46 is broken. This conflicted with #2579847: /node/add is lacking cacheability metadata, causes problems when cached by Dynamic Page Cache and "Use admin theme when editing or creating content" is turned off.
This is relative to #28.
Comment #50
fabianx commentedBack to RTBC
Comment #52
fabianx commentedAdding triage tag; it was also RTBC before RC deadline.
Comment #53
alexpottGiven the recent discussions on #2527126: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them and the fact that that patch removes support for cacheable redirects (through a side effect) I think it is important that we introduce proper cacheable redirects during RC.
It'd been great if the IS can be updated with some of the discussions about previous support of redirect caching in Drupal 8 as a justification for this been done during RC.
Comment #54
catchWhy is this necessary? Shouldn't caching for redirects be opt-in?
Or should 302s default to not cacheable and 301s default to cacheable?
I deliberately didn't re-read this issue, just trying to figure out the dependencies between this and #2527126: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them.
Comment #55
catchComment #56
wim leersLunch now, but I'll try to answer all questions here unless @znerol beats me to it.
Comment #57
wim leersDone.
Comment #58
fabianx commentedIMHO it is more than enough to say:
#2476407: Use CacheableResponseInterface to determine which responses should be cached made only CacheableResponseInterface responses cacheable by default.
We want to support that for redirects as well as there is a ContributedProjectBlocker for redirect module.
And cacheable redirects are a very common need.
Comment #59
wim leersI agree. It looks like @znerol wanted to make as many redirects as possible cacheable. But of course, often times redirects happen after something happened. Which means the overall response is not actually cacheable.
Reviewing this patch again:
This is indeed a perfectly cacheable redirect response, and it doesn't require any additional cacheability metadata, because it doesn't depend on any external information. It's a hard-coded redirect essentially.
This is very much worth having, but test coverage is missing.
This is the API change that is not acceptable anymore.
i.e. these changes need to be reverted.
Then all of these can go away.
This one actually is cacheable, but… on 99% of sites,
node.addis an admin route, so this won't be cached. Also, the redirect only happens if there's only one node type, which is something even less likely.So we'd be introducing a change here during RC with very, very limited benefit. So let's revert this one too.
Given we no longer modify
UrlGeneratorTrait::redirect(), all these changes become obsolete too.The changes in this file should also no longer be necessary.
This is an appropriate bugfix, but belongs in a separate issue. Especially now that this is the only remaining change in this file.
Rather than doing this here and for
TrustedRedirectResponse, it makes more sense to just do it ONCE inSecuredRedirectResponse.With that drastic reduction in scope, the changes in this patch are now limited to:
CacheableRedirectResponse.RedirectLeadingSlashesSubscriber.SecuredRedirectResponseto extendCacheableRedirectResponse.TrustedRedirectResponseTest.Only 4 files touched, only 6 KB patch, no disruption, no BC break, and much, much simpler to review :)
Comment #61
wim leersSo now I know why #60.10 wasn't implemented in the original patch:
I guess I'll have to move that back to the individual subclasses.
Comment #62
wim leersAdded
abstract class CacheableSecuredRedirectResponse extends SecuredRedirectResponse implements CacheableResponseInterface {}to\Drupal\Core\Routing, whichLocalRedirectResponseandTrustedRedirectResponsenow both extend. Still prevents that duplication, so still better than duplicating the logic in both of them.Comment #63
wim leersIS updated: updated proposed resolution and beta eval.
Comment #64
wim leersWhen/if this lands, we should also update https://www.drupal.org/developing/api/8/response.
Comment #65
znerol commentedI realize now that there is not too much benefit in making every redirect response cacheable - especially those generated after form submissions. The motivation to open this issue was #2476407: Use CacheableResponseInterface to determine which responses should be cached which killed cacheability for redirect responses completely. Now that this patch went away from making controller and form redirects cacheable by default, we should look for those cases where caching those responses is desirable (i.e. static redirect responses in non-admin pages).
Candidates can be found like this:
find . -type f -and -not -ipath '*test*' -and -not -ipath '*form*' | xargs git grep -- -l '->redirect('The following occurrences likely should be made cacheable:
AccessDeniedSubscriber::onException()(redirect anonymous users from/userto/user/login)CommentController::redirectNode()SearchController::redirectSearchPage()Comment #66
wim leers#65: woah, nice command!
I agree that #65 is very valuable to do. But those responses weren't cached in Page Cache before either, right? This issue is both a contrib blocker and blocking #2527126: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them. I think it's therefore important to keep this patch as minimal as possible. I think it'd be easier to make the changes you're suggesting in a follow-up issue, since they're purely nice-to-haves.
Of course, if committers would like to see that happen here, then I'd be happy to oblige.
Comment #67
znerol commentedThey were, and that is exactly why I opened this issue. The redirect responses are cached despite the fact that they are lacking any cacheability metadata, like described in the original issue summary.
This is with d5c827e:
And this is with HEAD:
So, the response will not be stored anymore by external caches/browsers. Also as soon as #2527126: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them lands, the response will not be cached anymore in the internal cache. It seems to me that we are solving the lack of metadata problem by introducing a performance regression.
Comment #68
wim leersThat is the commit before #2476407: Use CacheableResponseInterface to determine which responses should be cached landed. So your motivation for this issue is fixing the regression for redirects in core, caused by #2476407.
But, the "contributed project blocker" bit and the "blocking #2527126" bit are the reasons this is an rc target.
The regression caused by #2476407 is merely unfortunate, its impact is very low. And I think it can easily be argued that those redirects being cacheable was mostly accidental, because there was no test coverage for them.
So, in summary: this issue is an rc target because it blocks another rc target. This patch is now a pure API addition. That makes it much easier to commit during RC. I realize that that is maybe different from your original intent with this issue, but would you not be okay with moving the "make those 3 redirects cacheable" to a separate issue? I'm happy to open that issue and file the patch.
Comment #69
znerol commentedRe #68 sure.
RTBC +1
Comment #70
wim leers#69: created that issue: #2594835: Make high-impact redirects that can be cached cacheable.
Comment #71
fabianx commentedRTBC
Comment #72
alexpottCommitted e900ded and pushed to 8.0.x. Thanks!
Missing @param fixed on commit.
Comment #74
wim leersThanks!
Updated https://www.drupal.org/developing/api/8/response (diff: https://www.drupal.org/node/2590001/revisions/view/8990175/9006681).
Unpostponed #2573923: Introduce a CacheableRedirectResponse and use it where appropriate.
Comment #75
wim leersAlso updated https://www.drupal.org/developing/api/8/response/cacheable-response-inte....
Comment #76
berdirNote: This already broke redirect.module, because it returns trusted responses and this now replaced the manually set cache tags header. Fixed here: https://github.com/md-systems/redirect/commit/0e8a0e4f305ec086b61bc3aea0...
The good part is that this means that #2527126: Only send X-Drupal-Cache-Tags and -Contexts headers when developer explicitly enables them is even less an API change now, since it no longer breaks redirect.module as it was already broken and fixed. That issue won't affect it anymore ;)