MINOR: cache: Add Expires header value parsing
When no Cache-Control max-age or s-maxage information is present in a
cached response, we need to parse the Expires header value (RFC 7234#5.3).
An invalid Expires date value or a date earlier than the reception date
will make the cache_entry stale upon creation.
For now, the Cache-Control and Expires headers are parsed after the
insertion of the response in the cache so even if the parsing of the
Expires results in an already stale entry, the entry will exist in the
cache.
diff --git a/reg-tests/cache/expires.vtc b/reg-tests/cache/expires.vtc
new file mode 100644
index 0000000..51c7487
--- /dev/null
+++ b/reg-tests/cache/expires.vtc
@@ -0,0 +1,122 @@
+varnishtest "Expires support"
+
+#REQUIRE_VERSION=2.3
+
+feature ignore_unknown_macro
+
+server s1 {
+ rxreq
+ txresp -nolen -hdr "Transfer-Encoding: chunked" \
+ -hdr "Cache-Control: max-age=5"
+ chunkedlen 15
+ chunkedlen 15
+ chunkedlen 15
+ chunkedlen 0
+} -start
+
+server s2 {
+ rxreq
+ txresp -nolen -hdr "Transfer-Encoding: chunked"
+ chunkedlen 16
+ chunkedlen 16
+ chunkedlen 16
+ chunkedlen 0
+} -start
+
+server s3 {
+ rxreq
+ txresp -nolen -hdr "Transfer-Encoding: chunked"
+ chunkedlen 16
+ chunkedlen 16
+ chunkedlen 16
+ chunkedlen 0
+
+ rxreq
+ txresp -nolen -hdr "Transfer-Encoding: chunked"
+ 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}"
+ use_backend cache_control_be if { path_beg /cache_control }
+ use_backend future_expires_be if { path_beg /future }
+ default_backend past_expires_be
+
+ backend cache_control_be
+ # Expires header should be ignored since a Cache-Control one is present
+ http-request cache-use my_cache
+ server www ${s1_addr}:${s1_port}
+ http-response set-header X-Cache-Hit %[res.cache_hit]
+ http-response set-header Expires %[date(-1),http_date]
+ http-response cache-store my_cache
+
+ backend future_expires_be
+ # Expires value set in the future (current_time+5s)
+ http-request cache-use my_cache
+ server www ${s2_addr}:${s2_port}
+ http-response set-header X-Cache-Hit %[res.cache_hit]
+ http-response set-header Expires %[date(5),http_date]
+ http-response cache-store my_cache
+
+ backend past_expires_be
+ # Expires value set in the past
+ http-request cache-use my_cache
+ server www ${s3_addr}:${s3_port}
+ http-response set-header X-Cache-Hit %[res.cache_hit]
+ http-response set-header Expires %[date(-1),http_date]
+ 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 "/cache_control"
+ rxresp
+ expect resp.status == 200
+ expect resp.bodylen == 45
+
+ txreq -url "/cache_control"
+ rxresp
+ expect resp.status == 200
+ expect resp.bodylen == 45
+ expect resp.http.X-Cache-Hit == 1
+
+ txreq -url "/future"
+ rxresp
+ expect resp.status == 200
+ expect resp.bodylen == 48
+
+ txreq -url "/future"
+ rxresp
+ expect resp.status == 200
+ expect resp.bodylen == 48
+ expect resp.http.X-Cache-Hit == 1
+
+ txreq -url "/past"
+ rxresp
+ expect resp.status == 200
+ expect resp.bodylen == 48
+
+ txreq -url "/past"
+ rxresp
+ expect resp.status == 200
+ expect resp.bodylen == 51
+ expect resp.http.X-Cache-Hit == 0
+
+} -run
+
diff --git a/src/cache.c b/src/cache.c
index 2c37ca1..a73f266 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -482,6 +482,9 @@
struct http_hdr_ctx ctx = { .blk = NULL };
int smaxage = -1;
int maxage = -1;
+ int expires = -1;
+ struct tm tm = {};
+ time_t expires_val = 0;
while (http_find_header(htx, ist("cache-control"), &ctx, 0)) {
char *value;
@@ -505,7 +508,28 @@
}
}
- /* TODO: Expires - Data */
+ /* Look for Expires header if no s-maxage or max-age Cache-Control data
+ * was found. */
+ if (maxage == -1 && smaxage == -1) {
+ ctx.blk = NULL;
+ if (http_find_header(htx, ist("expires"), &ctx, 1)) {
+ if (parse_http_date(istptr(ctx.value), istlen(ctx.value), &tm)) {
+ expires_val = my_timegm(&tm);
+ /* A request having an expiring date earlier
+ * than the current date should be considered as
+ * stale. */
+ expires = (expires_val >= now.tv_sec) ?
+ (expires_val - now.tv_sec) : 0;
+ }
+ else {
+ /* Following RFC 7234#5.3, an invalid date
+ * format must be treated as a date in the past
+ * so the cache entry must be seen as already
+ * expired. */
+ expires = 0;
+ }
+ }
+ }
if (smaxage > 0)
@@ -514,6 +538,9 @@
if (maxage > 0)
return MIN(maxage, cache->maxage);
+ if (expires >= 0)
+ return MIN(expires, cache->maxage);
+
return cache->maxage;
}