-
Notifications
You must be signed in to change notification settings - Fork 382
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
cache_req_fsm: keep the cache object's Content-Length for HEAD always #4247
base: master
Are you sure you want to change the base?
Conversation
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.
LGTM, but this was not a thorough review.
sub vcl_backend_fetch { | ||
if (bereq.http.X-Fetch-Method) { | ||
set bereq.method = bereq.http.X-Fetch-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.
In this example configuration, the X-Fetch-Method
headers can't be unset here before sending the request to the backend or it breaks the Vary
part, right? I don't mind sending an extra header to my backend but it's one thing that differs from the restart
-based solution.
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.
Correct. We need to (un)set the header before the cache lookup such that the right variant gets hit, if present. For a miss, the header gets copied to the backend request and, when it completes (after vcl_backend_response {}
returns), the header's value gets added to the Vary specification for that cache object.
So, in short, the header needs to be present during cache lookup and at the end of vcl_backend_response {}
. For practical reasons, it is also needed to signal the backend side to activate the Vary
handling.
With these requirements in mind, we can change the code to not send the header by deleting it in vcl_backend_fetch {}
and restoring it in vcl_backend_response {}
, but we need a vmod to do so. Here's how the test case adjustment looks like with a taskvar.bool
object as a simple marker to activate the vary handling:
diff --git a/bin/varnishtest/tests/r04245.vtc b/bin/varnishtest/tests/r04245.vtc
index 27244e053..42982b79a 100644
--- a/bin/varnishtest/tests/r04245.vtc
+++ b/bin/varnishtest/tests/r04245.vtc
@@ -13,6 +13,12 @@ server s1 {
} -start
varnish v1 -vcl+backend {
+ import taskvar;
+
+ sub vcl_init {
+ new vary_x_fetch_method = taskvar.bool();
+ }
+
sub vcl_recv {
if (req.method == "HEAD") {
set req.http.X-Fetch-Method = "HEAD";
@@ -24,13 +30,17 @@ varnish v1 -vcl+backend {
sub vcl_backend_fetch {
if (bereq.http.X-Fetch-Method) {
set bereq.method = bereq.http.X-Fetch-Method;
+ # use marker to avoid sending the header to the backend
+ unset bereq.http.X-Fetch-Method;
+ vary_x_fetch_method.set(true);
}
}
sub vcl_backend_response {
# NOTE: this use of Vary is specific to this case, it is
# usually WRONG to only set Vary for a specific condition
- if (bereq.http.X-Fetch-Method) {
+ if (vary_x_fetch_method.get()) {
+ set bereq.http.X-Fetch-Method = bereq.method;
if (beresp.http.Vary) {
set beresp.http.Vary += ", X-Fetch-Method";
} else {
For the purpose within the varnish-cache tree, we only use bundled vmods, so this change can not be applied to the proposed patch.
An even simpler way would be to use bereq.method == "HEAD"
as the marker in vcl_backend_response {}
, which should be possible if the additional logic is only used for HEAD
. That is, it should work exactly as in the test case, but might cause trouble in real world VCL:
diff --git a/bin/varnishtest/tests/r04245.vtc b/bin/varnishtest/tests/r04245.vtc
index 27244e053..44edbd5bc 100644
--- a/bin/varnishtest/tests/r04245.vtc
+++ b/bin/varnishtest/tests/r04245.vtc
@@ -24,13 +24,15 @@ varnish v1 -vcl+backend {
sub vcl_backend_fetch {
if (bereq.http.X-Fetch-Method) {
set bereq.method = bereq.http.X-Fetch-Method;
+ unset bereq.http.X-Fetch-Method;
}
}
sub vcl_backend_response {
# NOTE: this use of Vary is specific to this case, it is
# usually WRONG to only set Vary for a specific condition
- if (bereq.http.X-Fetch-Method) {
+ if (bereq.method == "HEAD") {
+ set bereq.http.X-Fetch-Method = bereq.method;
if (beresp.http.Vary) {
set beresp.http.Vary += ", X-Fetch-Method";
} else {
notes from bugwash:
my own homework:
|
Previously, we would only keep the Content-Length header for HEAD requests on hit-for-miss objects, now we simply keep it always to enable "fallback" caching of HEAD requests. The added vtc implements the basics of the logic to enable the (reasonable) use case documented in varnishcache#2107 (comment) but using Vary instead of cache key modification plus restart. Fixes varnishcache#4245
b59f59a
to
9770c6f
Compare
homework: why does the current code work? diff --git a/bin/varnishd/cache/cache_req_fsm.c b/bin/varnishd/cache/cache_req_fsm.c
index bbcb3824f..91ec23780 100644
--- a/bin/varnishd/cache/cache_req_fsm.c
+++ b/bin/varnishd/cache/cache_req_fsm.c
@@ -493,6 +493,7 @@ cnt_transmit(struct worker *wrk, struct req *req)
* filters have had a chance to chew on it, but that
* would negate the "pass for huge objects" use case.
*/
+ VSLb(req->vsl, SLT_Debug, "HEAD with OC_F_HFM");
} else {
http_Unset(req->resp, H_Content_Length);
if (req->resp_len >= 0)
so the answer is: Because we set |
I the following, I refer to a response which, by definition, does not have an HTTP body (CONNECT or HEAD request and any response with a 1xx (Informational), 204 (No Content), or 304 (Not Modified) status code) as without body, and all others as with body, even if the body may be empty.
I have pondered the how and worked on an implementation, and for now, I am unhappy with what I have: Coming up with a better alternative is surprisingly hard, because, for most cases, we either create or recreate the Hence, I lean towards a very simple solution to make the change after VCL has finished: Add a "removeCL" filter, which
This implies that filters will also need to run for responses without a body, but, at least from my perspective, this is already an overdue change for consistency. The vcl interface would be simple, for example: sub vcl_deliver {
set resp.filters += " removeCL";
} @dridi I guess you might have opinions, do you? |
We specifically made content-length and transfer-encoding read-only headers because of their role in HTTP framing (especially HTTP/1.x). So it shouldn't be direct control.
This is interesting, but I don't really understand what you are proposing.
In both cases we end up without a body delivery, so this really looks like a case for
We may want to also discard content-encoding if there was a body.
No opinion, I haven't given much thought, but I am sensitive to the consistency argument. I'm pretty sure that filters today already fiddle with headers, so having headers-only filters make sense (making the I'm not a big fan of header manipulation in core or VMOD code, but I can several cases in favor of this kind of "rendez-vous point" ability to tweak headers before/after a delivery/fetch. Essentially cases where VCL syntax is too limited. |
Re @dridi
Yes, I understand what we did and why, but still this might not have been the best solution to the problem. This ticket is about the response to
If we wanted to have a correct So, for the case of the Now the bugwash decision was that VCL should have a way to prevent sending In order to not repeat myself, please re-read #4247 (comment) with the above in mind. The problem is that
It's not the same, because for |
While @dridi and me should find concensus, I think this PR could be merged. The question how to avoid sending |
Previously, we would only keep the Content-Length header for HEAD requests on hit-for-miss objects, now we simply keep it always to enable "fallback" caching of HEAD requests.
The added vtc implements the basics of the logic to enable the (reasonable) use case documented in
#2107 (comment) but using Vary instead of cache key modification plus restart.
Fixes #4245