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.
This commit is contained in:
Christopher Faulet 2020-01-15 15:19:50 +01:00
parent ac2412fee8
commit 5885775de1
7 changed files with 156 additions and 75 deletions

View File

@ -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 */

View File

@ -23,6 +23,9 @@
#ifndef _TYPES_HTTP_HTX_H
#define _TYPES_HTTP_HTX_H
#include <ebistree.h>
#include <common/buf.h>
#include <common/htx.h>
#include <common/ist.h>
@ -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 */

View File

@ -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 */

View File

@ -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 <status_code> and <file> as arguments.\n", file, linenum, args[0]);
ha_alert("parsing [%s:%d] : %s: expects <status_code> and <file> 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;

View File

@ -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);

View File

@ -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];
}

View File

@ -29,6 +29,7 @@
#include <proto/sample.h>
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 <buf> and 1 is returned. On error, 0 is returned and
* an error message is written into the <errmsg> buffer. It is this function
* responsibility to allocate <buf> 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 <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 <errmsg> 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 <msg> into an HTX message. On success, the
* result is stored in <buf> and 1 is returned. On error, 0 is returned and an
* error message is written into the <errmsg> buffer. It is this function
* responsibility to allocate <buf> and to release it if an error occurred.
/* Convert the raw http message <msg> into an HTX message. On sucess, the HTX
* message is returned. On error, NULL is returned and an error message is
* written into the <errmsg> 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 <file> for the status code
* <status>. On success, it returns the HTTP_ERR_* value corresponding to the
* specified status code and it allocated and fills the buffer <buf> with the
* HTX message. On error, it returns -1 and nothing is allocated.
* <status>. 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 <status>. <url> is used as location url for the
* redirect. <errloc> 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 <buf> with the HTX
* message. On error, it returns -1 and nothing is allocated.
* redirect. <errloc> 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;
}
/************************************************************************/