From 5885775de17b47d34b98ffa00adde6dc6e0b3ec8 Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Wed, 15 Jan 2020 15:19:50 +0100 Subject: [PATCH] MEDIUM: http-htx/proxy: Use a global and centralized storage for HTTP error messages All custom HTTP errors are now stored in a global tree. Proxies use a references on these messages. The key used for errorfile directives is the file name as specified in the configuration. For errorloc directives, a key is created using the redirect code and the url. This means that the same custom error message is now stored only once. It may be used in several proxies or for several status code, it is only parsed and stored once. --- include/proto/http_htx.h | 8 +- include/types/http_htx.h | 11 +++ include/types/proxy.h | 2 +- src/cfgparse-listen.c | 35 ++++----- src/haproxy.c | 4 - src/http_ana.c | 8 +- src/http_htx.c | 163 ++++++++++++++++++++++++++++----------- 7 files changed, 156 insertions(+), 75 deletions(-) diff --git a/include/proto/http_htx.h b/include/proto/http_htx.h index 0cec9db58..3f8634010 100644 --- a/include/proto/http_htx.h +++ b/include/proto/http_htx.h @@ -49,9 +49,9 @@ unsigned int http_get_htx_fhdr(const struct htx *htx, const struct ist hdr, int occ, struct http_hdr_ctx *ctx, char **vptr, size_t *vlen); int http_str_to_htx(struct buffer *buf, struct ist raw); -int http_load_errorfile(const char *file, struct buffer *buf, char **errmsg); -int http_load_errormsg(const struct ist msg, struct buffer *buf, char **errmsg); -int http_parse_errorfile(int status, const char *file, struct buffer *buf, char **errmsg); -int http_parse_errorloc(int errloc, int status, const char *url, struct buffer *buf, char **errmsg); +struct buffer *http_load_errorfile(const char *file, char **errmsg); +struct buffer *http_load_errormsg(const char *key, const struct ist msg, char **errmsg); +struct buffer *http_parse_errorfile(int status, const char *file, char **errmsg); +struct buffer *http_parse_errorloc(int errloc, int status, const char *url, char **errmsg); #endif /* _PROTO_HTTP_HTX_H */ diff --git a/include/types/http_htx.h b/include/types/http_htx.h index 3f9a7f4b0..d9c3052f9 100644 --- a/include/types/http_htx.h +++ b/include/types/http_htx.h @@ -23,6 +23,9 @@ #ifndef _TYPES_HTTP_HTX_H #define _TYPES_HTTP_HTX_H +#include + +#include #include #include @@ -34,4 +37,12 @@ struct http_hdr_ctx { uint16_t lws_after; }; +/* A custom HTTP error message load from a row file and converted in HTX. The + * node key is the file path. + */ +struct http_error { + struct buffer msg; + struct ebpt_node node; +}; + #endif /* _TYPES_HTTP_HTX_H */ diff --git a/include/types/proxy.h b/include/types/proxy.h index 6ea96b3ad..232634dec 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -427,7 +427,7 @@ struct proxy { char *check_path; /* PATH environment to use for external agent checks */ char *expect_str; /* http-check expected content : string or text version of the regex */ struct my_regex *expect_regex; /* http-check expected content */ - struct buffer errmsg[HTTP_ERR_SIZE]; /* default or customized error messages for known errors */ + struct buffer *errmsg[HTTP_ERR_SIZE]; /* default or customized error messages for known errors */ int uuid; /* universally unique proxy ID, used for SNMP */ unsigned int backlog; /* force the frontend's listen backlog */ unsigned long bind_proc; /* bitmask of processes using this proxy */ diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index b42fe536d..9177181e7 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -262,8 +262,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) } /* initialize error relocations */ - for (rc = 0; rc < HTTP_ERR_SIZE; rc++) - chunk_dup(&curproxy->errmsg[rc], &defproxy.errmsg[rc]); + memcpy(&curproxy->errmsg, &defproxy.errmsg, sizeof(defproxy.errmsg)); if (curproxy->cap & PR_CAP_FE) { curproxy->maxconn = defproxy.maxconn; @@ -503,8 +502,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) free(defproxy.conf.logformat_sd_string); free(defproxy.conf.lfsd_file); - for (rc = 0; rc < HTTP_ERR_SIZE; rc++) - chunk_destroy(&defproxy.errmsg[rc]); + memset(&defproxy.errmsg, 0, sizeof(defproxy.errmsg)); /* we cannot free uri_auth because it might already be used */ init_default_instance(); @@ -3810,8 +3808,8 @@ stats_error_parsing: else if (!strcmp(args[0], "errorloc") || !strcmp(args[0], "errorloc302") || !strcmp(args[0], "errorloc303")) { /* error location */ - struct buffer chk; - int errloc, status, rc; + struct buffer *msg; + int errloc, status; if (warnifnotcap(curproxy, PR_CAP_FE | PR_CAP_BE, file, linenum, args[0], NULL)) err_code |= ERR_WARN; @@ -3824,39 +3822,38 @@ stats_error_parsing: status = atol(args[1]); errloc = (!strcmp(args[0], "errorloc303") ? 303 : 302); - rc = http_parse_errorloc(errloc, status, args[2], &chk, &errmsg); - if (rc == -1) { + msg = http_parse_errorloc(errloc, status, args[2], &errmsg); + if (!msg) { ha_alert("parsing [%s:%d] : %s: %s\n", file, linenum, args[0], errmsg); err_code |= ERR_ALERT | ERR_FATAL; goto out; } - - chunk_destroy(&curproxy->errmsg[rc]); - curproxy->errmsg[rc] = chk; + rc = http_get_status_idx(status); + curproxy->errmsg[rc] = msg; } else if (!strcmp(args[0], "errorfile")) { /* error message from a file */ - struct buffer chk; - int status, rc; + struct buffer *msg; + int status; if (warnifnotcap(curproxy, PR_CAP_FE | PR_CAP_BE, file, linenum, args[0], NULL)) err_code |= ERR_WARN; if (*(args[1]) == 0 || *(args[2]) == 0) { - ha_alert("parsing [%s:%d] : <%s> expects and as arguments.\n", file, linenum, args[0]); + ha_alert("parsing [%s:%d] : %s: expects and as arguments.\n", + file, linenum, args[0]); err_code |= ERR_ALERT | ERR_FATAL; goto out; } status = atol(args[1]); - rc = http_parse_errorfile(status, args[2], &chk, &errmsg); - if (rc == -1) { + msg = http_parse_errorfile(status, args[2], &errmsg); + if (!msg) { ha_alert("parsing [%s:%d] : %s: %s\n", file, linenum, args[0], errmsg); err_code |= ERR_ALERT | ERR_FATAL; goto out; } - - chunk_destroy(&curproxy->errmsg[rc]); - curproxy->errmsg[rc] = chk; + rc = http_get_status_idx(status); + curproxy->errmsg[rc] = msg; } else { struct cfg_kw_list *kwl; diff --git a/src/haproxy.c b/src/haproxy.c index 12d59c383..78143d7e7 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -2331,7 +2331,6 @@ void deinit(void) struct post_deinit_fct *pdf; struct proxy_deinit_fct *pxdf; struct server_deinit_fct *srvdf; - int i; deinit_signals(); while (p) { @@ -2359,9 +2358,6 @@ void deinit(void) free(p->conf.logformat_sd_string); free(p->conf.lfsd_file); - for (i = 0; i < HTTP_ERR_SIZE; i++) - chunk_destroy(&p->errmsg[i]); - list_for_each_entry_safe(cond, condb, &p->mon_fail_cond, list) { LIST_DEL(&cond->list); prune_acl_cond(cond); diff --git a/src/http_ana.c b/src/http_ana.c index e100589a8..c6e0bc7b5 100644 --- a/src/http_ana.c +++ b/src/http_ana.c @@ -4597,10 +4597,10 @@ struct buffer *http_error_message(struct stream *s) { const int msgnum = http_get_status_idx(s->txn->status); - if (s->be->errmsg[msgnum].area) - return &s->be->errmsg[msgnum]; - else if (strm_fe(s)->errmsg[msgnum].area) - return &strm_fe(s)->errmsg[msgnum]; + if (s->be->errmsg[msgnum]) + return s->be->errmsg[msgnum]; + else if (strm_fe(s)->errmsg[msgnum]) + return strm_fe(s)->errmsg[msgnum]; else return &http_err_chunks[msgnum]; } diff --git a/src/http_htx.c b/src/http_htx.c index 2b368a4ec..33edcac80 100644 --- a/src/http_htx.c +++ b/src/http_htx.c @@ -29,6 +29,7 @@ #include struct buffer http_err_chunks[HTTP_ERR_SIZE]; +struct eb_root http_error_messages = EB_ROOT; static int http_update_authority(struct htx *htx, struct htx_sl *sl, const struct ist host); static int http_update_host(struct htx *htx, struct htx_sl *sl, const struct ist uri); @@ -836,21 +837,50 @@ end: return err_code; } -REGISTER_CONFIG_POSTPARSER("http_htx", http_htx_init); - -/* Reads content of an error file and convert it in an HTX message. On success, - * the result is stored in and 1 is returned. On error, 0 is returned and - * an error message is written into the buffer. It is this function - * responsibility to allocate and to release it if an error occurred. - */ -int http_load_errorfile(const char *file, struct buffer *buf, char **errmsg) +static void http_htx_deinit(void) { + struct ebpt_node *node, *next; + struct http_error *http_err; + + node = ebpt_first(&http_error_messages); + while (node) { + next = ebpt_next(node); + ebpt_delete(node); + http_err = container_of(node, typeof(*http_err), node); + chunk_destroy(&http_err->msg); + free(node->key); + free(http_err); + node = next; + } +} + +REGISTER_CONFIG_POSTPARSER("http_htx", http_htx_init); +REGISTER_POST_DEINIT(http_htx_deinit); + +/* Reads content of the error file and convert it into an HTX message. On + * success, the HTX message is returned. On error, NULL is returned and an error + * message is written into the buffer. + */ +struct buffer *http_load_errorfile(const char *file, char **errmsg) +{ + struct buffer *buf = NULL; + struct buffer chk; + struct ebpt_node *node; + struct http_error *http_err; struct stat stat; char *err = NULL; int errnum, errlen; int fd = -1; - int ret = 0; + /* already loaded */ + node = ebis_lookup_len(&http_error_messages, file, strlen(file)); + if (node) { + http_err = container_of(node, typeof(*http_err), node); + buf = &http_err->msg; + goto out; + } + + /* Read the error file content */ fd = open(file, O_RDONLY); if ((fd < 0) || (fstat(fd, &stat) < 0)) { memprintf(errmsg, "error opening file '%s'.", file); @@ -877,78 +907,125 @@ int http_load_errorfile(const char *file, struct buffer *buf, char **errmsg) goto out; } - if (!http_str_to_htx(buf, ist2(err, errlen))) { - memprintf(errmsg, "unable to convert custom error message file '%s' in HTX.", file); + /* Create the node corresponding to the error file */ + http_err = calloc(1, sizeof(*http_err)); + if (!http_err) { + memprintf(errmsg, "out of memory."); + goto out; + } + http_err->node.key = strdup(file); + if (!http_err->node.key) { + memprintf(errmsg, "out of memory."); goto out; } - ret = 1; + /* Convert the error file into an HTX message */ + if (!http_str_to_htx(&chk, ist2(err, errlen))) { + memprintf(errmsg, "unable to convert custom error message file '%s' in HTX.", file); + free(http_err->node.key); + free(http_err); + goto out; + } + + /* Insert the node in the tree and return the HTX message */ + http_err->msg = chk; + ebis_insert(&http_error_messages, &http_err->node); + buf = &http_err->msg; + out: if (fd >= 0) close(fd); free(err); - return ret; + return buf; } -/* Convert the raw http message into an HTX message. On success, the - * result is stored in and 1 is returned. On error, 0 is returned and an - * error message is written into the buffer. It is this function - * responsibility to allocate and to release it if an error occurred. +/* Convert the raw http message into an HTX message. On sucess, the HTX + * message is returned. On error, NULL is returned and an error message is + * written into the buffer. */ -int http_load_errormsg(const struct ist msg, struct buffer *buf, char **errmsg) +struct buffer *http_load_errormsg(const char *key, const struct ist msg, char **errmsg) { - int ret = 0; + struct buffer *buf = NULL; + struct buffer chk; + struct ebpt_node *node; + struct http_error *http_err; - /* Convert the error file into an HTX message */ - if (!http_str_to_htx(buf, msg)) { - memprintf(errmsg, "unable to convert message in HTX."); + /* already loaded */ + node = ebis_lookup_len(&http_error_messages, key, strlen(key)); + if (node) { + http_err = container_of(node, typeof(*http_err), node); + buf = &http_err->msg; + goto out; + } + /* Create the node corresponding to the error file */ + http_err = calloc(1, sizeof(*http_err)); + if (!http_err) { + memprintf(errmsg, "out of memory."); + goto out; + } + http_err->node.key = strdup(key); + if (!http_err->node.key) { + memprintf(errmsg, "out of memory."); goto out; } - ret = 1; + /* Convert the error file into an HTX message */ + if (!http_str_to_htx(&chk, msg)) { + memprintf(errmsg, "unable to convert message in HTX."); + free(http_err->node.key); + free(http_err); + goto out; + } + + /* Insert the node in the tree and return the HTX message */ + http_err->msg = chk; + ebis_insert(&http_error_messages, &http_err->node); + buf = &http_err->msg; out: - return ret; + return buf; } - /* This function parses the raw HTTP error file for the status code - * . On success, it returns the HTTP_ERR_* value corresponding to the - * specified status code and it allocated and fills the buffer with the - * HTX message. On error, it returns -1 and nothing is allocated. + * . It returns NULL if there is any error, otherwise it return the + * corresponding HTX message. */ -int http_parse_errorfile(int status, const char *file, struct buffer *buf, char **errmsg) +struct buffer *http_parse_errorfile(int status, const char *file, char **errmsg) { - int rc, ret = -1; + struct buffer *buf = NULL; + int rc; for (rc = 0; rc < HTTP_ERR_SIZE; rc++) { if (http_err_codes[rc] == status) { - if (http_load_errorfile(file, buf, errmsg)) - ret = rc; + buf = http_load_errorfile(file, errmsg); break; } } if (rc >= HTTP_ERR_SIZE) memprintf(errmsg, "status code '%d' not handled.", status); - return ret; + return buf; } /* This function creates HTX error message corresponding to a redirect message * for the status code . is used as location url for the - * redirect. is used to know if it is a 302 or a 303 redirect. On - * success, it returns the HTTP_ERR_* value corresponding to the specified - * status code and it allocated and fills the buffer with the HTX - * message. On error, it returns -1 and nothing is allocated. + * redirect. is used to know if it is a 302 or a 303 redirect. It + * returns NULL if there is any error, otherwise it return the corresponding HTX + * message. */ -int http_parse_errorloc(int errloc, int status, const char *url, struct buffer *buf, char **errmsg) +struct buffer *http_parse_errorloc(int errloc, int status, const char *url, char **errmsg) { + struct buffer *buf = NULL; const char *msg; - char *err = NULL; + char *key = NULL, *err = NULL; int rc, errlen; - int ret = -1; for (rc = 0; rc < HTTP_ERR_SIZE; rc++) { if (http_err_codes[rc] == status) { + /* Create the error key */ + if (!memprintf(&key, "errorloc%d %s", errloc, url)) { + memprintf(errmsg, "out of memory."); + goto out; + } /* Create the error message */ msg = (errloc == 302 ? HTTP_302 : HTTP_303); errlen = strlen(msg) + strlen(url) + 5; @@ -960,8 +1037,7 @@ int http_parse_errorloc(int errloc, int status, const char *url, struct buffer * errlen = snprintf(err, errlen, "%s%s\r\n\r\n", msg, url); /* Load it */ - if (http_load_errormsg(ist2(err, errlen), buf, errmsg)) - ret = rc; + buf = http_load_errormsg(key, ist2(err, errlen), errmsg); break; } } @@ -969,8 +1045,9 @@ int http_parse_errorloc(int errloc, int status, const char *url, struct buffer * if (rc >= HTTP_ERR_SIZE) memprintf(errmsg, "status code '%d' not handled.", status); out: + free(key); free(err); - return ret; + return buf; } /************************************************************************/