Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
Forms are an important part of Drupal, but currently they are uncacheable (CSRF tokens, parameters can come from anywhere), but not marked as such.
Also there is no way to opt-in for a form to be render cached explicitly.
Proposed resolution
- Ensure forms are not cached by default as they can vary on things.
- Ensure forms that specify how they can be cached (and that maybe don't need CSRF) are cacheable.
Remaining tasks
- Discuss
- Do it
User interface changes
API changes
Data model changes
Comment | File | Size | Author |
---|---|---|---|
#18 | forms_cacheability-2526472-18.patch | 3.58 KB | Wim Leers |
Comments
Comment #1
Wim LeersNote that the patch in #2429617: Make D8 2x as fast: Dynamic Page Cache: context-dependent page caching (for *all* users!) currently sets
max-age = 0
on all forms.Comment #2
Fabianx CreditAttribution: Fabianx as a volunteer commentedNice, can a form opt back in?
Comment #3
dawehnerSounds like a good plan!
We need to ensure that login form, exposed forms and the search block are marked as cacheable.
Comment #4
Wim LeersIndeed!
Comment #5
catchThe login form is not render cached at the moment. That is one of the most expensive things on non-page cached anonymous requests and was partly why I opened #2503755: Switch from user login block to login menu link and search block in standard profile.
user/register is less important but could be added to the list.
Once #2503429: [PP-*] Allow both AJAX and non-AJAX forms to POST to dedicated URLs is done we've got the chance to mark more things cacheable, but I think that's probably the full list until then.
Comment #6
Wim LeersComment #7
Wim LeersWhat about this?
Comment #8
Crell CreditAttribution: Crell at Palantir.net commentedComment here seems unnecessary.
Nit: Since forms are exclusively GET or POST, shouldn't we instead flip it and say "if it's a POST and no cache is set, then set it to no-cache?" (Both code and comment.)
Looks like a reasonable and direct approach, but needs a basic test or two.
Comment #9
Wim LeersOh, and absolutely, this needs tests!
Let's first hear what others think about this overall approach.
Comment #10
moshe weitzman CreditAttribution: moshe weitzman at Acquia commentedThe comment form on node detail pages is a common killer - #2552873: node/1 flamegraphs
Comment #11
BerdirNot sure if that will help.
The comment form is personalized to the user. Not exactly sure how the changed form caching now affects this as we rebuild the form now anyway on submission.
Comment #12
Wim LeersAlternative solutions to the problem outlined in #10: move the comment form to a separate page (which is just a setting) and/or load it via JS.
Comment #13
moshe weitzman CreditAttribution: moshe weitzman at Acquia commentedIf none of these are fruitful, I'm fine with moving the form or loading via JS.
Comment #14
dawehnerSeriously, but this would be just a horrible movement. Just think any website, like the one I'm currently posting one, the comment form on a new page simply does not make sense for most sites.
Comment #15
Wim Leers#13:
FormState
, at least in HEAD. If the form logic is changed so that that is no longer the case, then that'd be possible.#14:
It wouldn't make sense for sites/use cases where the majority of users are commenting. So, d.o and forums would be a bad match for that.
But… on most news sites, commerce sites, etc. only a tiny minority of users are actually commenting. Heck, on many news sites, you even have to load the already written comments manually: Ars Technica, The Verge, et cetera. Any site using Disqus only loads the comments as soon as you scroll far enough down.
So, I don't think it'd be that horrible.
Comment #16
Wim LeersBut, #10–#15 are not what this issue is about. This issue is about forms in general, not the tricky aspects of the comment form in specific.
Comment #17
andypostComment form is a separate issue, also there's AJAX Comments
Comment #18
Wim LeersComments #10 through #17 are indeed a distraction. They're not what this issue is about.
The last relevant comments were #8 (a review by Crell) and #9 (my answer to that review). Addressed all his feedback and wrote tests.
Comment #19
Crell CreditAttribution: Crell as a volunteer commentedSeems legit.
Comment #20
effulgentsia CreditAttribution: effulgentsia at Acquia commentedIf it's only CSRF tokens, then why not put the max-age=0 on the token element instead of the entire form? If it's because parameters can come from anywhere, then why does that concern not apply to GET forms?
Comment #21
Wim LeersThe general answer
You're right that this will need to be refined further.
This issue is only about setting sensible defaults.
The sensible defaults being introduced here is:
In other words, this ensures that the 99% case will not be cached incorrectly.
The specific answer
You're technically right here. See the above for the reasoning in this patch.
If you feel strongly that we should mark all forms as uncacheable (
max-age = 0
) unless they opt out by specifying their own cacheability, I'm fine with that.Comment #22
effulgentsia CreditAttribution: effulgentsia at Acquia commentedI'm skeptical about this. In D7, by default, all forms that are accessible to anonymous users can be page cached. #18 changes that, because it doesn't distinguish anonymous from authenticated. So I don't think we want to say "by default, forms should have max-age=0". Instead, maybe what we want is for forms by default to not get cached for authenticated users, because we don't trust module developers who write forms to assign cache contexts everywhere that they do something dynamic. This to me seems very similar to the same concern we have in #2556889: [policy, no patch] Decide if SmartCache is still in scope for 8.0 and whether remaining risks require additional mitigation for non-form controllers. In that issue, we're discussing the possibility to make SmartCache require explicit per-route or per-controller opt-in. If we end up doing that, I think then we can apply the same opted out by default rule to form routes and with the same API to opt-in. So I suggest postponing form-wide cache changes on that discussion.
But, I think it makes sense for this issue (or a child issue) to set max-age = 0 on the
'form_token'
element. I think that might be enough to unblock this part of the SmartCache patch, since that would bubble up to the 99% of forms that we're concerned about. #2463567: Push CSRF tokens for forms to placeholders + #lazy_builder might end up fixing the problem of form tokens breaking the cacheability of forms, but I think it's still worthwhile to first commit a patch that sets max-age = 0 on 'form_token' to reflect its current reality, and then work on that issue to improve that situation.Comment #23
Wim LeersThis is not true. The page cache happily caches pages with
max-age = 0
for anonymous users.But forms don't always have routes. Forms first and foremost have IDs, and they can be (and are) embedded anywhere in the page.
This is totally true. Opened #2559011: Ensure form tokens are marked max-age=0.
Comment #24
Wim LeersSo, #2559011: Ensure form tokens are marked max-age=0 has now landed, but turns out it's not enough. Quoting the comment I just posted on that issue:
Given that #2559011 + #2561775 together already solve the important part of this issue, demoting this to normal.
Comment #25
Wim LeersSo, we did #2559011: Ensure form tokens are marked max-age=0 and now we also did #2504139: Blocks containing a form include the form action in the cache, so they always submit to the first URL the form was viewed at (the more efficient alternative solution of #2561775: Forms without $form['#action'] set get their action automatically generated based on current path + query args: cacheability metadata is missing). Together, they solve this problem. Closing this as a duplicate.
Comment #26
Wim LeersTo clarify: you can make a form opt out from being marked as uncacheable by specifying
$form['#token'] = FALSE;
. If your form truly is cacheable, then it also shouldn't need the token. But be aware that this has security implications!