From: Christopher Faulet Date: Fri, 15 May 2020 13:47:44 +0000 (+0200) Subject: MINOR: http-htx/proxy: Add http-error directive using http return syntax X-Git-Tag: v2.2-dev8~6 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3b967c12102d9cfac9b404ec8b47510444ae3c93;p=thirdparty%2Fhaproxy.git 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 ==> http-errror status errorfile --- diff --git a/doc/configuration.txt b/doc/configuration.txt index cd75de5669..9eaec2ab7d 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 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 [ ...] 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 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 @@ -3907,7 +3908,7 @@ errorloc303 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 @@ -4852,6 +4853,83 @@ http-check unset-var() http-check unset-var(check.port) +http-error status [content-type ] + [ { default-errorfiles | errorfile | errorfiles | + file | lf-file | string | lf-string } ] + [ hdr ]* + 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 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 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 . 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 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 designates the http-errors section to use to import + the error message with the status code . If no + such message is found, the proxy's error messages are + considered. + + 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. is + considered as a raw string. + + string specifies the raw string to use as response payload. + The content-type must always be set as argument to + "content-type". + + lf-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. is + evaluated as a log-format string. + + lf-string specifies the log-format string to use as response + payload. The content-type must always be set as + argument to "content-type". + + hdr adds to the response the HTTP header field whose name + is specified in and whose value is defined by + , 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 [options...] [ { if | unless } ] Access control for Layer 7 requests diff --git a/include/types/arg.h b/include/types/arg.h index 80e0b0a7be..60fd007e51 100644 --- a/include/types/arg.h +++ b/include/types/arg.h @@ -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 */ diff --git a/reg-tests/http-errorfiles/http-error.vtc b/reg-tests/http-errorfiles/http-error.vtc new file mode 100644 index 0000000000..b03f2ace70 --- /dev/null +++ b/reg-tests/http-errorfiles/http-error.vtc @@ -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 == + 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 == + 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 diff --git a/src/http_htx.c b/src/http_htx.c index 78a5553d6d..aabf1a5984 100644 --- a/src/http_htx.c +++ b/src/http_htx.c @@ -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 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 }, }}; diff --git a/src/log.c b/src/log.c index 0aa043f255..78f1a9e0b6 100644 --- a/src/log.c +++ b/src/log.c @@ -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 */ } diff --git a/src/sample.c b/src/sample.c index 47e74b5eea..b223547f07 100644 --- a/src/sample.c +++ b/src/sample.c @@ -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 */