diff --git a/doc/configuration.txt b/doc/configuration.txt index cd75de566..9eaec2ab7 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 80e0b0a7b..60fd007e5 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 000000000..b03f2ace7 --- /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 78a5553d6..aabf1a598 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 0aa043f25..78f1a9e0b 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 47e74b5ee..b223547f0 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 */