William, find below a very dirty proof of concept patch for issue 821
https://github.com/haproxy/haproxy/issues/821 With the following example configuration: listen fe mode http bind *:8080 http-request cache-use foo http-response cache-store foo server foo localhost:8081 frontend be mode http bind *:8081 http-request return content-type text/plain lf-string stuff hdr etag '"stuff"' cache foo total-max-size 4 I get a proper 304 Not Modified without any valgrind complaints for some manual example requests: $ http localhost:8080 'if-none-match: "stuff"' HTTP/1.1 200 OK content-length: 5 content-type: text/plain etag: "stuff" stuff $ http localhost:8080 'if-none-match: "stuff"' HTTP/1.1 304 Not Modified age: 1 content-length: 5 content-type: text/plain etag: "stuff" $ http localhost:8080 'if-none-match: "stuff2"' HTTP/1.1 200 OK age: 3 content-length: 5 content-type: text/plain etag: "stuff" stuff Before continuing cleaning this up and properly handling both strong and weak ETags according to the RFC I'd like to have a first review to see whether I am generally on the right track there or whether I am doing dumb stuff that will cause issues under certain circumstances :-) Best regards Tim Düsterhus Apply with `git am --scissors` to automatically cut the commit message. -- >8 -- see GitHub issue #821 --- src/cache.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/cache.c b/src/cache.c index 533b902bf..b8802bb06 100644 --- a/src/cache.c +++ b/src/cache.c @@ -4,6 +4,8 @@ * Copyright 2017 HAProxy Technologies * William Lallemand <wlallem...@haproxy.com> * + * Copyright 2020 Tim Duesterhus <t...@bastelstu.be> + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version @@ -875,6 +877,7 @@ static void http_cache_io_handler(struct appctx *appctx) unsigned int len; size_t ret, total = 0; + req_htx = htxbuf(&req->buf); res_htx = htxbuf(&res->buf); total = res_htx->data; @@ -899,15 +902,31 @@ static void http_cache_io_handler(struct appctx *appctx) } if (appctx->st0 == HTX_CACHE_HEADER) { + struct http_hdr_ctx inm_ctx = { .blk = NULL }; + int etag_match = 0; + /* Headers must be dump at once. Otherwise it is an error */ len = first->len - sizeof(*cache_ptr) - appctx->ctx.cache.sent; ret = htx_cache_dump_msg(appctx, res_htx, len, HTX_BLK_EOH); - if (!ret || (htx_get_tail_type(res_htx) != HTX_BLK_EOH) || - !htx_cache_add_age_hdr(appctx, res_htx)) + if (!ret || (htx_get_tail_type(res_htx) != HTX_BLK_EOH)) + goto error; + + if (http_find_header(req_htx, ist("if-none-match"), &inm_ctx, 0)) { + struct http_hdr_ctx etag_ctx = { .blk = NULL }; + if (http_find_header(res_htx, ist("etag"), &etag_ctx, 0)) { + if (isteq(inm_ctx.value, etag_ctx.value)) { + etag_match = 1; + http_replace_res_status(res_htx, (const struct ist) IST("304")); + http_replace_res_reason(res_htx, (const struct ist) IST("Not Modified")); + } + } + } + + if (!htx_cache_add_age_hdr(appctx, res_htx)) goto error; /* Skip response body for HEAD requests */ - if (si_strm(si)->txn->meth == HTTP_METH_HEAD) + if (si_strm(si)->txn->meth == HTTP_METH_HEAD || etag_match) appctx->st0 = HTX_CACHE_EOM; else appctx->st0 = HTX_CACHE_DATA; -- 2.28.0