MINOR: http-htx/proxy: Add http-error directive using http return syntax

The http-error directive can now be used instead of errorfile to define an error
message in a proxy section (including default sections). This directive uses the
same syntax that http return rules. The only real difference is the limitation
on status code that may be specified. Only status codes supported by errorfile
directives are supported for this new directive. Parsing of errorfile directive
remains independent from http-error parsing. But functionally, it may be
expressed in terms of http-errors :

  errorfile <status> <file> ==> http-errror status <status> errorfile <file>
This commit is contained in:
Christopher Faulet 2020-05-15 15:47:44 +02:00
parent 97e466c9e3
commit 3b967c1210
6 changed files with 247 additions and 8 deletions

View File

@ -2659,6 +2659,7 @@ http-check expect X - X X
http-check send-state X - X X
http-check set-var X - X X
http-check unset-var X - X X
http-error X X X X
http-request - X X X
http-response - X X X
http-reuse X - X X
@ -3810,7 +3811,7 @@ errorfile <code> <file>
simple method for developing those files consists in associating them to the
403 status code and interrogating a blocked URL.
See also : "errorloc", "errorloc302", "errorloc303"
See also : "http-error", "errorloc", "errorloc302", "errorloc303"
Example :
errorfile 400 /etc/haproxy/errorfiles/400badreq.http
@ -3839,8 +3840,8 @@ errorfiles <name> [<code> ...]
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.
See also : "http-error", "errorfile", "errorloc", "errorloc302" ,
"errorloc303" and section 3.8 about http-errors.
Example :
errorfiles generic
@ -3877,7 +3878,7 @@ errorloc302 <code> <url>
status code, indicating to the client that the URL must be fetched with a GET
request.
See also : "errorfile", "errorloc303"
See also : "http-error", "errorfile", "errorloc303"
errorloc303 <code> <url>
@ -3907,7 +3908,7 @@ errorloc303 <code> <url>
possible that some very old browsers designed before HTTP/1.1 do not support
it, but no such problem has been reported till now.
See also : "errorfile", "errorloc", "errorloc302"
See also : "http-error", "errorfile", "errorloc", "errorloc302"
email-alert from <emailaddr>
@ -4852,6 +4853,83 @@ http-check unset-var(<var-name>)
http-check unset-var(check.port)
http-error status <code> [content-type <type>]
[ { default-errorfiles | errorfile <file> | errorfiles <name> |
file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
[ hdr <name> <fmt> ]*
Defines a custom error message to use instead of errors generated by HAProxy.
May be used in sections : defaults | frontend | listen | backend
yes | yes | yes | yes
Arguments :
staus <code> is the HTTP status code. It must be specified.
Currently, HAProxy is capable of generating codes
200, 400, 403, 404, 405, 408, 410, 425, 429, 500,
502, 503, and 504.
content-type <type> is the response content type, for instance
"text/plain". This parameter is ignored and should be
omitted when an errorfile is configured or when the
payload is empty. Otherwise, it must be defined.
default-errorfiles Reset the previously defined error message for current
proxy for the status <code>. If used on a backend, the
frontend error message is used, if defined. If used on
a frontend, the default error message is used.
errorfile <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.
errorfiles <name> designates the http-errors section to use to import
the error message with the status code <code>. If no
such message is found, the proxy's error messages are
considered.
file <file> specifies the file to use as response payload. If the
file is not empty, its content-type must be set as
argument to "content-type", otherwise, any
"content-type" argument is ignored. <file> is
considered as a raw string.
string <str> specifies the raw string to use as response payload.
The content-type must always be set as argument to
"content-type".
lf-file <file> specifies the file to use as response payload. If the
file is not empty, its content-type must be set as
argument to "content-type", otherwise, any
"content-type" argument is ignored. <file> is
evaluated as a log-format string.
lf-string <str> specifies the log-format string to use as response
payload. The content-type must always be set as
argument to "content-type".
hdr <name> <fmt> adds to the response the HTTP header field whose name
is specified in <name> and whose value is defined by
<fmt>, which follows to the log-format rules.
This parameter is ignored if an errorfile is used.
This directive may be used instead of "errorfile", to define a custom error
message. As "errorfile" directive, it is used for errors detected and
returned by HAProxy. If an errorfile is defined, it is parsed when HAProxy
starts and must be valid according to the HTTP standards. The generated
response must not exceed the configured buffer size (BUFFSIZE), otherwise an
internal error will be returned. Finally, if you consider to use some
http-after-response rules to rewrite these errors, the reserved buffer space
should be available (see "tune.maxrewrite").
The files are read at the same time as the configuration and kept in memory.
For this reason, the errors continue to be returned even when the process is
chrooted, and no file change is considered while the process is running.
See also : "errorfile", "errorfiles", "errorloc", "errorloc302",
"errorloc303" and section 3.8 about http-errors.
http-request <action> [options...] [ { if | unless } <condition> ]
Access control for Layer 7 requests

View File

@ -82,6 +82,7 @@ enum {
ARGC_SPOE, /* spoe message args */
ARGC_UBK, /* use_backend message */
ARGC_USRV, /* use-server message */
ARGC_HERR, /* http-error */
};
/* flags used when compiling and executing regex */

View File

@ -0,0 +1,75 @@
varnishtest "Test the http-error directive"
#REQUIRE_VERSION=2.2
# This config tests the http-error directive.
feature ignore_unknown_macro
haproxy h1 -conf {
http-errors errors-1
errorfile 400 ${testdir}/errors/400-1.http
errorfile 403 ${testdir}/errors/403-1.http
errorfile 404 ${testdir}/errors/404-1.http
errorfile 500 ${testdir}/errors/500-1.http
defaults
mode http
timeout connect 1s
timeout client 1s
timeout server 1s
errorfile 400 ${testdir}/errors/400.http
errorfile 404 ${testdir}/errors/404.http
frontend fe1
bind "fd@${fe1}"
http-error status 400
http-error status 403 default-errorfiles
http-error status 404 errorfiles errors-1
http-error status 500 errorfile ${testdir}/errors/500.http
http-error status 200 content-type "text/plain" hdr x-path "path=%[path]" lf-string "The path is \"%[path]\""
http-request return status 200 default-errorfiles if { path /200 }
http-request deny deny_status 400 if { path /400 }
http-request deny deny_status 403 if { path /403 }
http-request deny deny_status 404 if { path /404 }
http-request deny deny_status 500 if { path /500 }
} -start
client c1r1 -connect ${h1_fe1_sock} {
txreq -req GET -url /200
rxresp
expect resp.status == 200
expect resp.http.x-path == "path=/200"
expect resp.http.content-type == "text/plain"
expect resp.body == "The path is \"/200\""
} -run
client c1r2 -connect ${h1_fe1_sock} {
txreq -req GET -url /400
rxresp
expect resp.status == 400
expect resp.http.x-err-type == <undef>
expect resp.http.content-length == 0
} -run
client c1r3 -connect ${h1_fe1_sock} {
txreq -req GET -url /403
rxresp
expect resp.status == 403
expect resp.http.x-err-type == <undef>
expect resp.http.content-length == 93
expect resp.http.content-type == "text/html"
} -run
client c1r3 -connect ${h1_fe1_sock} {
txreq -req GET -url /404
rxresp
expect resp.status == 404
expect resp.http.x-err-type == "errors-1"
} -run
client c1r4 -connect ${h1_fe1_sock} {
txreq -req GET -url /500
rxresp
expect resp.status == 500
expect resp.http.x-err-type == "default"
} -run

View File

@ -1335,9 +1335,12 @@ struct http_reply *http_parse_http_reply(const char **args, int *orig_arg, struc
reply->type = HTTP_REPLY_EMPTY;
reply->status = default_status;
cap = ((px->conf.args.ctx == ARGC_HRQ)
? ((px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR)
: ((px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR));
if (px->conf.args.ctx == ARGC_HERR)
cap = (SMP_VAL_REQUEST | SMP_VAL_RESPONSE);
else
cap = ((px->conf.args.ctx == ARGC_HRQ)
? ((px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR)
: ((px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR));
cur_arg = *orig_arg;
while (*args[cur_arg]) {
@ -1837,6 +1840,80 @@ static int proxy_parse_errorfiles(char **args, int section, struct proxy *curpx,
goto out;
}
/* Parses the "http-error" proxy keyword */
static int proxy_parse_http_error(char **args, int section, struct proxy *curpx,
struct proxy *defpx, const char *file, int line,
char **errmsg)
{
struct conf_errors *conf_err;
struct http_reply *reply = NULL;
int rc, cur_arg, ret = 0;
if (warnifnotcap(curpx, PR_CAP_FE | PR_CAP_BE, file, line, args[0], NULL)) {
ret = 1;
goto out;
}
cur_arg = 1;
curpx->conf.args.ctx = ARGC_HERR;
reply = http_parse_http_reply((const char **)args, &cur_arg, curpx, 0, errmsg);
if (!reply) {
memprintf(errmsg, "%s : %s", args[0], *errmsg);
goto error;
}
else if (!reply->status) {
memprintf(errmsg, "%s : expects at least a <status> as arguments.\n", args[0]);
goto error;
}
for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
if (http_err_codes[rc] == reply->status)
break;
}
if (rc >= HTTP_ERR_SIZE) {
memprintf(errmsg, "%s: status code '%d' not handled.", args[0], reply->status);
goto error;
}
if (*args[cur_arg]) {
memprintf(errmsg, "%s : unknown keyword '%s'.", args[0], args[cur_arg]);
goto error;
}
conf_err = calloc(1, sizeof(*conf_err));
if (!conf_err) {
memprintf(errmsg, "%s : out of memory.", args[0]);
goto error;
}
if (reply->type == HTTP_REPLY_ERRFILES) {
int rc = http_get_status_idx(reply->status);
conf_err->type = 2;
conf_err->info.errorfiles.name = reply->body.http_errors;
conf_err->info.errorfiles.status[rc] = 2;
reply->body.http_errors = NULL;
release_http_reply(reply);
}
else {
conf_err->type = 1;
conf_err->info.errorfile.status = reply->status;
conf_err->info.errorfile.reply = reply;
LIST_ADDQ(&http_replies_list, &reply->list);
}
conf_err->file = strdup(file);
conf_err->line = line;
LIST_ADDQ(&curpx->conf.errors, &conf_err->list);
out:
return ret;
error:
release_http_reply(reply);
ret = -1;
goto out;
}
/* Check "errorfiles" proxy keyword */
static int proxy_check_errors(struct proxy *px)
{
@ -1849,6 +1926,10 @@ static int proxy_check_errors(struct proxy *px)
/* errorfile */
rc = http_get_status_idx(conf_err->info.errorfile.status);
px->replies[rc] = conf_err->info.errorfile.reply;
/* For proxy, to rely on default replies, just don't reference a reply */
if (px->replies[rc]->type == HTTP_REPLY_ERRMSG && !px->replies[rc]->body.errmsg)
px->replies[rc] = NULL;
}
else {
/* errorfiles */
@ -2069,6 +2150,7 @@ static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_LISTEN, "errorloc303", proxy_parse_errorloc },
{ CFG_LISTEN, "errorfile", proxy_parse_errorfile },
{ CFG_LISTEN, "errorfiles", proxy_parse_errorfiles },
{ CFG_LISTEN, "http-error", proxy_parse_http_error },
{ 0, NULL, NULL },
}};

View File

@ -310,6 +310,8 @@ static inline const char *fmt_directive(const struct proxy *curproxy)
return "spoe-message";
case ARGC_UBK:
return "use_backend";
case ARGC_HERR:
return "http-error";
default:
return "undefined(please report this bug)"; /* must never happen */
}

View File

@ -1132,6 +1132,7 @@ int smp_resolve_args(struct proxy *p)
case ARGC_ACL: ctx = "ACL keyword"; break;
case ARGC_SRV: where = "in server directive in"; break;
case ARGC_SPOE: where = "in spoe-message directive in"; break;
case ARGC_HERR: where = "in http-error directive in"; break;
}
/* set a few default settings */