From 53161d81b87e7953840f8e2c7453d7fd12cb6d13 Mon Sep 17 00:00:00 2001 From: Remi Tricot-Le Breton Date: Fri, 23 Oct 2020 10:51:28 +0200 Subject: [PATCH] MINOR: cache: Process the If-Modified-Since header in conditional requests If a client sends a conditional request containing an If-Modified-Since header (and no If-None-Match header), we try to compare the date with the one stored in the cache entry (coming either from a Last-Modified head, or a Date header, or corresponding to the first response's reception time). If the request's date is earlier than the stored one, we send a "304 Not Modified" response back. Otherwise, the stored is sent (through a 200 OK response). This resolves GitHub issue #821. --- reg-tests/cache/if-modified-since.vtc | 145 ++++++++++++++++++++++++++ src/cache.c | 39 ++++++- 2 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 reg-tests/cache/if-modified-since.vtc diff --git a/reg-tests/cache/if-modified-since.vtc b/reg-tests/cache/if-modified-since.vtc new file mode 100644 index 0000000000..af8cbf0b8e --- /dev/null +++ b/reg-tests/cache/if-modified-since.vtc @@ -0,0 +1,145 @@ +varnishtest "If-Modified-Since support" + +#REQUIRE_VERSION=2.3 + +feature ignore_unknown_macro + +server s1 { + # Response containing a "Last-Modified" field + rxreq + expect req.url == "/last_modified" + txresp -nolen -hdr "Transfer-Encoding: chunked" \ + -hdr "Last-Modified: Thu, 15 Oct 2020 22:23:24 GMT" + chunkedlen 15 + chunkedlen 15 + chunkedlen 15 + chunkedlen 0 + + # Response containing a "Date" field + rxreq + expect req.url == "/date" + txresp -nolen -hdr "Transfer-Encoding: chunked" \ + -hdr "Date: Thu, 22 Oct 2020 16:51:12 GMT" + chunkedlen 16 + chunkedlen 16 + chunkedlen 16 + chunkedlen 0 + + # Response containing both a "Last-Modified" and a "Date" fields + # Should behave the same way as if the "Date" field was not here. + rxreq + expect req.url == "/last_modified_and_date" + txresp -nolen -hdr "Transfer-Encoding: chunked" \ + -hdr "Last-Modified: Thu, 15 Oct 2020 14:24:38 GMT" \ + -hdr "Date: Thu, 22 Oct 2020 16:51:12 GMT" + chunkedlen 17 + chunkedlen 17 + chunkedlen 17 + chunkedlen 0 +} -start + +haproxy h1 -conf { + defaults + mode http + ${no-htx} option http-use-htx + timeout connect 1s + timeout client 1s + timeout server 1s + + frontend fe + bind "fd@${fe}" + default_backend test + + backend test + http-request cache-use my_cache + server www ${s1_addr}:${s1_port} + http-response cache-store my_cache + + cache my_cache + total-max-size 3 + max-age 20 + max-object-size 3072 +} -start + + +client c1 -connect ${h1_fe_sock} { + txreq -url "/last_modified" + rxresp + expect resp.status == 200 + expect resp.bodylen == 45 + + txreq -url "/date" + rxresp + expect resp.status == 200 + expect resp.bodylen == 48 + + txreq -url "/last_modified_and_date" + rxresp + expect resp.status == 200 + expect resp.bodylen == 51 + + + # Earlier date + # "Last-Modified" version + txreq -url "/last_modified" \ + -hdr "If-Modified-Since: Thu, 15 Oct 2020 00:00:01 GMT" + rxresp + expect resp.status == 200 + expect resp.bodylen == 45 + # "Date" version + txreq -url "/date" \ + -hdr "If-Modified-Since: Thu, 01 Oct 2020 00:00:01 GMT" + rxresp + expect resp.status == 200 + expect resp.bodylen == 48 + # "Last-Modified" and "Date" version + txreq -url "/last_modified_and_date" \ + -hdr "If-Modified-Since: Thu, 15 Oct 2020 00:00:01 GMT" + rxresp + expect resp.status == 200 + expect resp.bodylen == 51 + + + # Same date + # "Last-Modified" version + txreq -url "/last_modified" \ + -hdr "If-Modified-Since: Thu, 15 Oct 2020 22:23:24 GMT" + rxresp + expect resp.status == 304 + expect resp.bodylen == 0 + # "Date" version + txreq -url "/date" \ + -hdr "If-Modified-Since: Thu, 22 Oct 2020 16:51:12 GMT" + rxresp + expect resp.status == 304 + expect resp.bodylen == 0 + # "Last-Modified" and "Date" version + txreq -url "/last_modified_and_date" \ + -hdr "If-Modified-Since: Thu, 15 Oct 2020 16:51:12 GMT" + rxresp + expect resp.status == 304 + expect resp.bodylen == 0 + + + # Later date + # "Last-Modified" version + txreq -url "/last_modified" \ + -hdr "If-Modified-Since: Thu, 22 Oct 2020 23:00:00 GMT" + rxresp + expect resp.status == 304 + expect resp.bodylen == 0 + # "Date" version + txreq -url "/date" \ + -hdr "If-Modified-Since: Thu, 22 Oct 2020 23:00:00 GMT" + rxresp + expect resp.status == 304 + expect resp.bodylen == 0 + # "Last-Modified" and "Date" version + txreq -url "/last_modified_and_date" \ + -hdr "If-Modified-Since: Thu, 22 Oct 2020 23:00:00 GMT" + rxresp + expect resp.status == 304 + expect resp.bodylen == 0 + +} -run + diff --git a/src/cache.c b/src/cache.c index 29de7cda21..a98ca66e62 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1183,7 +1183,13 @@ int sha1_hosturi(struct stream *s) * matches, a "304 Not Modified" response should be sent instead of the cached * data. * Although unlikely in a GET/HEAD request, the "If-None-Match: *" syntax is - * valid and should receive a "304 Not Modified" response (RFC 7434#4.3.2). + * valid and should receive a "304 Not Modified" response (RFC 7234#4.3.2). + * + * If no "If-None-Match" header was found, look for an "If-Modified-Since" + * header and compare its value (date) to the one stored in the cache_entry. + * If the request's date is later than the cached one, we also send a + * "304 Not Modified" response (see RFCs 7232#3.3 and 7234#4.3.2). + * * Returns 1 if "304 Not Modified" should be sent, 0 otherwise. */ static int should_send_notmodified_response(struct cache *cache, struct htx *htx, @@ -1194,14 +1200,16 @@ static int should_send_notmodified_response(struct cache *cache, struct htx *htx struct http_hdr_ctx ctx = { .blk = NULL }; struct ist cache_entry_etag = IST_NULL; struct buffer *etag_buffer = NULL; + int if_none_match_found = 0; - if (entry->etag_length == 0) - return 0; + struct tm tm = {}; + time_t if_modified_since = 0; /* If we find a "If-None-Match" header in the request, rebuild the - * cache_entry's ETag in order to perform comparisons. */ - /* There could be multiple "if-none-match" header lines. */ + * cache_entry's ETag in order to perform comparisons. + * There could be multiple "if-none-match" header lines. */ while (http_find_header(htx, ist("if-none-match"), &ctx, 0)) { + if_none_match_found = 1; /* A '*' matches everything. */ if (isteq(ctx.value, ist("*")) != 0) { @@ -1209,6 +1217,10 @@ static int should_send_notmodified_response(struct cache *cache, struct htx *htx break; } + /* No need to rebuild an etag if none was stored in the cache. */ + if (entry->etag_length == 0) + break; + /* Rebuild the stored ETag. */ if (etag_buffer == NULL) { etag_buffer = get_trash_chunk(); @@ -1230,6 +1242,23 @@ static int should_send_notmodified_response(struct cache *cache, struct htx *htx } } + /* If the request did not contain an "If-None-Match" header, we look for + * an "If-Modified-Since" header (see RFC 7232#3.3). */ + if (retval == 0 && if_none_match_found == 0) { + ctx.blk = NULL; + if (http_find_header(htx, ist("if-modified-since"), &ctx, 1)) { + if (parse_http_date(istptr(ctx.value), istlen(ctx.value), &tm)) { + if_modified_since = my_timegm(&tm); + + /* We send a "304 Not Modified" response if the + * entry's last modified date is earlier than + * the one found in the "If-Modified-Since" + * header. */ + retval = (entry->last_modified <= if_modified_since); + } + } + } + return retval; }