From 99a17a2d91f9044ea20bba6617048488aed80555 Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Tue, 11 Dec 2018 09:18:27 +0100 Subject: [PATCH] MEDIUM: cache: Require an explicit filter declaration if other filters are used As for the compression filter, the cache filter must be explicitly declared (using the filter keyword) if other filters than cache are used. It is mandatory to explicitly define the filters order. Documentation has been updated accordingly. --- doc/configuration.txt | 28 +++++++++++-- src/cache.c | 97 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 120 insertions(+), 5 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 18fcf96040..400ec6402c 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -107,6 +107,7 @@ Summary 9.1. Trace 9.2. HTTP compression 9.3. Stream Processing Offload Engine (SPOE) +9.4. Cache 10. Cache 10.1. Limitation @@ -17752,6 +17753,25 @@ Important note: The SPOE filter is highly experimental for now and was not heavily tested. It is really not production ready. So use it carefully. +9.4. Cache +---------- + +filter cache + + Arguments : + + is name of the cache section this filter will use. + +The cache uses a filter to store cacheable responses. The HTTP rules +"cache-store" and "cache-use" must be used to define how and when to use a +cache. By default the correpsonding filter is implicitly defined. And when no +other filter than cache is used, it is enough. But it is mandatory to +explicitly use a filter line to use a cache when two or more filters are used +for the same listener/frontend/backend. This is important to know the filters +evaluation order. + +See also : section 10 about cache. + 10. Cache --------- @@ -17789,9 +17809,11 @@ The cache won't store and won't deliver objects in these cases: - If the HTTP version of the request is smaller than 1.1 - If the request contains an Authorization header -Caution!: Due to the current limitation of the filters, it is not recommended -to use the cache with other filters. Using them can cause undefined behavior -if they modify the response (compression for example). +Caution!: For HAProxy version prior to 1.9, due to the limitation of the +filters, it is not recommended to use the cache with other filters. Using them +can cause undefined behavior if they modify the response (compression for +example). For HAProxy 1.9 and greater, it is safe, for HTX proxies only (see +"option http-use-htx" for details). 10.2. Setup ----------- diff --git a/src/cache.c b/src/cache.c index fd8a00ad3d..407d150f9d 100644 --- a/src/cache.c +++ b/src/cache.c @@ -45,6 +45,8 @@ #define CACHE_FLT_F_IGNORE_CNT_ENC 0x00000001 /* Ignore 'Content-Encoding' header when response is cached * if compression is already started */ +#define CACHE_FLT_F_IMPLICIT_DECL 0x00000002 /* The cache filtre was implicitly declared (ie without + * the filter keyword) */ const char *cache_store_flt_id = "cache store filter"; @@ -189,13 +191,20 @@ cache_store_check(struct proxy *px, struct flt_conf *fconf) * compressed in the cache. When the compression is after the cache, the * 'Content-encoding' header must be ignored because the response will * be stored uncompressed. The compression will be done on the cached - * response too. */ + * response too. Also check if the cache filter must be explicitly + * declaired or not. */ list_for_each_entry(f, &px->filter_configs, list) { if (f == fconf) { ignore = 1; continue; } + if ((f->id != fconf->id) && (cconf->flags & CACHE_FLT_F_IMPLICIT_DECL)) { + ha_alert("config: %s '%s': require an explicit filter declaration " + "to use the cache '%s'.\n", proxy_type_str(px), px->id, cache->id); + return 1; + } + if (f->id == http_comp_flt_id) { if (!(px->options2 & PR_O2_USE_HTX)) { ha_alert("config: %s '%s' : compression and cache filters cannot be " @@ -1264,7 +1273,7 @@ static int parse_cache_rule(struct proxy *proxy, const char *name, struct act_ru memprintf(err, "out of memory\n"); goto err; } - cconf->flags = 0; + cconf->flags = CACHE_FLT_F_IMPLICIT_DECL; cconf->c.name = strdup(name); if (!cconf->c.name) { memprintf(err, "out of memory\n"); @@ -1623,6 +1632,81 @@ struct flt_ops cache_ops = { }; + + +static int +parse_cache_flt(char **args, int *cur_arg, struct proxy *px, + struct flt_conf *fconf, char **err, void *private) +{ + struct flt_conf *f, *back; + struct cache_flt_conf *cconf; + char *name = NULL; + int pos = *cur_arg; + + /* Get the cache filter name*/ + if (!strcmp(args[pos], "cache")) { + if (!*args[pos + 1]) { + memprintf(err, "%s : expects an argument", args[pos]); + goto error; + } + name = strdup(args[pos + 1]); + if (!name) { + memprintf(err, "%s '%s' : out of memory", args[pos], args[pos + 1]); + goto error; + } + pos += 2; + } + + /* Check if an implicit filter with the same name already exists. If so, + * we remove the implicit filter to use the explicit one. */ + list_for_each_entry_safe(f, back, &px->filter_configs, list) { + if (f->id != cache_store_flt_id) + continue; + + cconf = f->conf; + if (strcmp(name, cconf->c.name)) { + cconf = NULL; + continue; + } + + if (!(cconf->flags & CACHE_FLT_F_IMPLICIT_DECL)) { + cconf = NULL; + memprintf(err, "%s: multiple explicit declarations of the cache filter '%s'", + px->id, name); + return -1; + } + + /* Remove the implicit filter. is kept for the explicit one */ + LIST_DEL(&f->list); + free(f); + free(name); + break; + } + + /* No implicit cache filter found, create configuration for the explicit one */ + if (!cconf) { + cconf = calloc(1, sizeof(*cconf)); + if (!cconf) { + memprintf(err, "%s: out of memory", args[*cur_arg]); + goto error; + } + cconf->c.name = name; + } + + cconf->flags = 0; + fconf->id = cache_store_flt_id; + fconf->conf = cconf; + fconf->ops = &cache_ops; + + *cur_arg = pos; + return 0; + + error: + free(name); + free(cconf); + return -1; +} + static int cli_parse_show_cache(char **args, char *payload, struct appctx *appctx, void *private) { if (!cli_has_level(appctx, ACCESS_LVL_ADMIN)) @@ -1686,6 +1770,15 @@ static int cli_io_handler_show_cache(struct appctx *appctx) } +/* Declare the filter parser for "cache" keyword */ +static struct flt_kw_list filter_kws = { "CACHE", { }, { + { "cache", parse_cache_flt, NULL }, + { NULL, NULL, NULL }, + } +}; + +INITCALL1(STG_REGISTER, flt_register_keywords, &filter_kws); + static struct cli_kw_list cli_kws = {{},{ { { "show", "cache", NULL }, "show cache : show cache status", cli_parse_show_cache, cli_io_handler_show_cache, NULL, NULL }, {{},}