MEDIUM: proxy: Add a directive to reference an http-errors section in a proxy
It is now possible to import in a proxy, fully or partially, error files
declared in an http-errors section. It may be done using the "errorfiles"
directive, followed by a name and optionally a list of status code. If there is
no status code specified, all error files of the http-errors section are
imported. Otherwise, only error files associated to the listed status code are
imported. For instance :
http-errors my-errors
errorfile 400 ...
errorfile 403 ...
errorfile 404 ...
frontend frt
errorfiles my-errors 403 404 # ==> error 400 not imported
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 1114f63..1d69380 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -52,6 +52,7 @@
3.5. Peers
3.6. Mailers
3.7. Programs
+3.8. HTTP-errors
4. Proxies
4.1. Proxy keywords matrix
@@ -2378,6 +2379,45 @@
program section.
+3.8. HTTP-errors
+----------------
+
+It is possible to globally declare several groups of HTTP errors, to be
+imported afterwards in any proxy section. Same group may be referenced at
+several places and can be fully or partially imported.
+
+http-errors <name>
+ Create a new http-errors group with the name <name>. It is an independent
+ section that may be referenced by one or more proxies using its name.
+
+errorfile <code> <file>
+ Associate a file contents to an HTTP error code
+
+ Arguments :
+ <code> is the HTTP status code. Currently, HAProxy is capable of
+ generating codes 200, 400, 403, 404, 405, 408, 410, 425, 429,
+ 500, 502, 503, and 504.
+
+ <file> designates a file containing the full HTTP response. It is
+ recommended to follow the common practice of appending ".http" to
+ the filename so that people do not confuse the response with HTML
+ error pages, and to use absolute paths, since files are read
+ before any chroot is performed.
+
+ Please referrers to "errorfile" keyword in section 4 for details.
+
+ Example:
+ http-errors website-1
+ errorfile 400 /etc/haproxy/errorfiles/site1/400.http
+ errorfile 404 /etc/haproxy/errorfiles/site1/404.http
+ errorfile 408 /dev/null # work around Chrome pre-connect bug
+
+ http-errors website-2
+ errorfile 400 /etc/haproxy/errorfiles/site2/400.http
+ errorfile 404 /etc/haproxy/errorfiles/site2/404.http
+ errorfile 408 /dev/null # work around Chrome pre-connect bug
+
+
4. Proxies
----------
@@ -2489,6 +2529,7 @@
email-alert to X X X X
enabled X X X X
errorfile X X X X
+errorfiles X X X X
errorloc X X X X
errorloc302 X X X X
-- keyword -------------------------- defaults - frontend - listen -- backend -
@@ -3651,6 +3692,34 @@
errorfile 503 /etc/haproxy/errorfiles/503sorry.http
+errorfiles <name> [<code> ...]
+ Import, fully or partially, the error files defined in the <name> http-errors
+ section.
+ May be used in sections : defaults | frontend | listen | backend
+ yes | yes | yes | yes
+ Arguments :
+ <name> is the name of an existing http-errors section.
+
+ <code> is a HTTP status code. Several status code may be listed.
+ Currently, HAProxy is capable of generating codes 200, 400, 403,
+ 404, 405, 408, 410, 425, 429, 500, 502, 503, and 504.
+
+ Errors defined in the http-errors section with the name <name> are imported
+ in the current proxy. If no status code is specified, all error files of the
+ http-errors section are imported. Otherwise, only error files associated to
+ the listed status code are imported. Those error files override the already
+ defined custom errors for the proxy. And they may be overridden by following
+ ones. Fonctionnly, it is exactly the same than declaring all error files by
+ hand using "errorfile" directives.
+
+ See also : "errorfile", "errorloc", "errorloc302" , "errorloc303" and section
+ 3.8 about http-errors.
+
+ Example :
+ errorfiles generic
+ errorfiles site-1 403 404
+
+
errorloc <code> <url>
errorloc302 <code> <url>
Return an HTTP redirection to a URL instead of errors generated by HAProxy
diff --git a/include/proto/http_htx.h b/include/proto/http_htx.h
index 3f86340..0b25d41 100644
--- a/include/proto/http_htx.h
+++ b/include/proto/http_htx.h
@@ -53,5 +53,7 @@
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);
+int proxy_dup_default_conf_errors(struct proxy *curpx, struct proxy *defpx, char **errmsg);
+void proxy_release_conf_errors(struct proxy *px);
#endif /* _PROTO_HTTP_HTX_H */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 232634d..c018c26 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -449,6 +449,7 @@
struct eb_root used_server_name; /* list of server names in use */
struct list bind; /* list of bind settings */
struct list listeners; /* list of listeners belonging to this frontend */
+ struct list errors; /* list of all custom error files */
struct arg_list args; /* sample arg list that need to be resolved */
struct ebpt_node by_name; /* proxies are stored sorted by name here */
char *logformat_string; /* log format string */
diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c
index 61bf7cd..86d1f33 100644
--- a/src/cfgparse-listen.c
+++ b/src/cfgparse-listen.c
@@ -262,7 +262,11 @@
}
/* initialize error relocations */
- memcpy(&curproxy->errmsg, &defproxy.errmsg, sizeof(defproxy.errmsg));
+ if (!proxy_dup_default_conf_errors(curproxy, &defproxy, &errmsg)) {
+ ha_alert("parsing [%s:%d] : proxy '%s' : %s\n", file, linenum, curproxy->id, errmsg);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
if (curproxy->cap & PR_CAP_FE) {
curproxy->maxconn = defproxy.maxconn;
@@ -502,7 +506,7 @@
free(defproxy.conf.logformat_sd_string);
free(defproxy.conf.lfsd_file);
- memset(&defproxy.errmsg, 0, sizeof(defproxy.errmsg));
+ proxy_release_conf_errors(&defproxy);
/* we cannot free uri_auth because it might already be used */
init_default_instance();
diff --git a/src/http_htx.c b/src/http_htx.c
index e848e37..016fd8f 100644
--- a/src/http_htx.c
+++ b/src/http_htx.c
@@ -32,6 +32,27 @@
struct eb_root http_error_messages = EB_ROOT;
struct list http_errors_list = LIST_HEAD_INIT(http_errors_list);
+/* The declaration of an errorfiles/errorfile directives. Used during config
+ * parsing only. */
+struct conf_errors {
+ char type; /* directive type (0: errorfiles, 1: errorfile) */
+ union {
+ struct {
+ int status; /* the status code associated to this error */
+ struct buffer *msg; /* the HTX error message */
+ } errorfile; /* describe an "errorfile" directive */
+ struct {
+ char *name; /* the http-errors section name */
+ char status[HTTP_ERR_SIZE]; /* list of status to import (0: ignore, 1: implicit import, 2: explicit import) */
+ } errorfiles; /* describe an "errorfiles" directive */
+ } info;
+
+ char *file; /* file where the directive appears */
+ int line; /* line where the directive appears */
+
+ struct list list; /* next conf_errors */
+};
+
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);
@@ -1064,8 +1085,10 @@
struct proxy *defpx, const char *file, int line,
char **errmsg)
{
+ struct conf_errors *conf_err;
struct buffer *msg;
- int errloc, status, rc, ret = 0;
+ int errloc, status;
+ int ret = 0;
if (warnifnotcap(curpx, PR_CAP_FE | PR_CAP_BE, file, line, args[0], NULL)) {
ret = 1;
@@ -1087,21 +1110,33 @@
goto out;
}
- rc = http_get_status_idx(status);
- curpx->errmsg[rc] = msg;
+ conf_err = calloc(1, sizeof(*conf_err));
+ if (!conf_err) {
+ memprintf(errmsg, "%s : out of memory.", args[0]);
+ ret = -1;
+ goto out;
+ }
+ conf_err->type = 1;
+ conf_err->info.errorfile.status = status;
+ conf_err->info.errorfile.msg = msg;
+ conf_err->file = strdup(file);
+ conf_err->line = line;
+ LIST_ADDQ(&curpx->conf.errors, &conf_err->list);
out:
return ret;
-}
+}
/* Parses the "errorfile" proxy keyword */
static int proxy_parse_errorfile(char **args, int section, struct proxy *curpx,
struct proxy *defpx, const char *file, int line,
char **errmsg)
{
+ struct conf_errors *conf_err;
struct buffer *msg;
- int status, rc, ret = 0;
+ int status;
+ int ret = 0;
if (warnifnotcap(curpx, PR_CAP_FE | PR_CAP_BE, file, line, args[0], NULL)) {
ret = 1;
@@ -1121,13 +1156,189 @@
ret = -1;
goto out;
}
+
+ conf_err = calloc(1, sizeof(*conf_err));
+ if (!conf_err) {
+ memprintf(errmsg, "%s : out of memory.", args[0]);
+ ret = -1;
+ goto out;
+ }
+ conf_err->type = 1;
+ conf_err->info.errorfile.status = status;
+ conf_err->info.errorfile.msg = msg;
+ conf_err->file = strdup(file);
+ conf_err->line = line;
+ LIST_ADDQ(&curpx->conf.errors, &conf_err->list);
+
+ out:
+ return ret;
+
+}
+
+/* Parses the "errorfiles" proxy keyword */
+static int proxy_parse_errorfiles(char **args, int section, struct proxy *curpx,
+ struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ struct conf_errors *conf_err = NULL;
+ char *name = NULL;
+ int rc, ret = 0;
+
+ if (warnifnotcap(curpx, PR_CAP_FE | PR_CAP_BE, file, line, args[0], NULL)) {
+ ret = 1;
+ goto out;
+ }
+
+ if (!*(args[1])) {
+ memprintf(err, "%s : expects <name> as argument.", args[0]);
+ ret = -1;
+ goto out;
+ }
+
+ name = strdup(args[1]);
+ conf_err = calloc(1, sizeof(*conf_err));
+ if (!name || !conf_err) {
+ memprintf(err, "%s : out of memory.", args[0]);
+ ret = -1;
+ goto error;
+ }
+ conf_err->type = 0;
+
+ conf_err->info.errorfiles.name = name;
+ if (!*(args[2])) {
+ for (rc = 0; rc < HTTP_ERR_SIZE; rc++)
+ conf_err->info.errorfiles.status[rc] = 1;
+ }
+ else {
+ int cur_arg, status;
+ for (cur_arg = 2; *(args[cur_arg]); cur_arg++) {
+ status = atol(args[cur_arg]);
+
+ for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
+ if (http_err_codes[rc] == status) {
+ conf_err->info.errorfiles.status[rc] = 2;
+ break;
+ }
+ }
+ if (rc >= HTTP_ERR_SIZE) {
+ memprintf(err, "%s : status code '%d' not handled.", args[0], status);
+ ret = -1;
+ goto out;
+ }
+ }
+ }
+ conf_err->file = strdup(file);
+ conf_err->line = line;
+ LIST_ADDQ(&curpx->conf.errors, &conf_err->list);
+ out:
+ return ret;
+
+ error:
+ free(name);
+ free(conf_err);
+ goto out;
+}
+
+/* Check "errorfiles" proxy keyword */
+static int proxy_check_errors(struct proxy *px)
+{
+ struct conf_errors *conf_err, *conf_err_back;
+ struct http_errors *http_errs;
+ int rc, err = 0;
+
+ list_for_each_entry_safe(conf_err, conf_err_back, &px->conf.errors, list) {
+ if (conf_err->type == 1) {
+ /* errorfile */
+ rc = http_get_status_idx(conf_err->info.errorfile.status);
+ px->errmsg[rc] = conf_err->info.errorfile.msg;
+ }
+ else {
+ /* errorfiles */
+ list_for_each_entry(http_errs, &http_errors_list, list) {
+ if (strcmp(http_errs->id, conf_err->info.errorfiles.name) == 0)
+ break;
+ }
- rc = http_get_status_idx(status);
- curpx->errmsg[rc] = msg;
+ /* unknown http-errors section */
+ if (&http_errs->list == &http_errors_list) {
+ ha_alert("config : proxy '%s': unknown http-errors section '%s' (at %s:%d).\n",
+ px->id, conf_err->info.errorfiles.name, conf_err->file, conf_err->line);
+ err |= ERR_ALERT | ERR_FATAL;
+ free(conf_err->info.errorfiles.name);
+ goto next;
+ }
+
+ free(conf_err->info.errorfiles.name);
+ for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
+ if (conf_err->info.errorfiles.status[rc] > 0) {
+ if (http_errs->errmsg[rc])
+ px->errmsg[rc] = http_errs->errmsg[rc];
+ else if (conf_err->info.errorfiles.status[rc] == 2)
+ ha_warning("config: proxy '%s' : status '%d' not declared in"
+ " http-errors section '%s' (at %s:%d).\n",
+ px->id, http_err_codes[rc], http_errs->id,
+ conf_err->file, conf_err->line);
+ }
+ }
+ }
+ next:
+ LIST_DEL(&conf_err->list);
+ free(conf_err->file);
+ free(conf_err);
+ }
out:
+ return err;
+}
+
+int proxy_dup_default_conf_errors(struct proxy *curpx, struct proxy *defpx, char **errmsg)
+{
+ struct conf_errors *conf_err, *new_conf_err = NULL;
+ int ret = 0;
+
+ list_for_each_entry(conf_err, &defpx->conf.errors, list) {
+ new_conf_err = calloc(1, sizeof(*new_conf_err));
+ if (!new_conf_err) {
+ memprintf(errmsg, "unable to duplicate default errors (out of memory).");
+ goto out;
+ }
+ new_conf_err->type = conf_err->type;
+ if (conf_err->type == 1) {
+ new_conf_err->info.errorfile.status = conf_err->info.errorfile.status;
+ new_conf_err->info.errorfile.msg = conf_err->info.errorfile.msg;
+ }
+ else {
+ new_conf_err->info.errorfiles.name = strdup(conf_err->info.errorfiles.name);
+ if (!new_conf_err->info.errorfiles.name) {
+ memprintf(errmsg, "unable to duplicate default errors (out of memory).");
+ goto out;
+ }
+ memcpy(&new_conf_err->info.errorfiles.status, &conf_err->info.errorfiles.status,
+ sizeof(conf_err->info.errorfiles.status));
+ }
+ new_conf_err->file = strdup(conf_err->file);
+ new_conf_err->line = conf_err->line;
+ LIST_ADDQ(&curpx->conf.errors, &new_conf_err->list);
+ new_conf_err = NULL;
+ }
+ ret = 1;
+
+ out:
+ free(new_conf_err);
return ret;
+}
+
+void proxy_release_conf_errors(struct proxy *px)
+{
+ struct conf_errors *conf_err, *conf_err_back;
+ list_for_each_entry_safe(conf_err, conf_err_back, &px->conf.errors, list) {
+ if (conf_err->type == 0)
+ free(conf_err->info.errorfiles.name);
+ LIST_DEL(&conf_err->list);
+ free(conf_err->file);
+ free(conf_err);
+ }
}
/*
@@ -1218,10 +1429,12 @@
{ CFG_LISTEN, "errorloc302", proxy_parse_errorloc },
{ CFG_LISTEN, "errorloc303", proxy_parse_errorloc },
{ CFG_LISTEN, "errorfile", proxy_parse_errorfile },
+ { CFG_LISTEN, "errorfiles", proxy_parse_errorfiles },
{ 0, NULL, NULL },
}};
INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
+REGISTER_POST_PROXY_CHECK(proxy_check_errors);
REGISTER_CONFIG_SECTION("http-errors", cfg_parse_http_errors, NULL);
diff --git a/src/proxy.c b/src/proxy.c
index aed32f9..dbce45d 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -873,6 +873,7 @@
LIST_INIT(&p->format_unique_id);
LIST_INIT(&p->conf.bind);
LIST_INIT(&p->conf.listeners);
+ LIST_INIT(&p->conf.errors);
LIST_INIT(&p->conf.args.list);
LIST_INIT(&p->tcpcheck_rules);
LIST_INIT(&p->filter_configs);