[MEDIUM] errorfile: use a local file to feed error messages

It is now possible to read error messages from local files,
using the 'errorfile' keyword. Those files are read during
parsing, so there's no I/O involved. They make it possible
to return custom error messages with custom status and headers.
This commit is contained in:
Willy Tarreau 2007-06-11 00:29:26 +02:00
parent 1ad7c6dd85
commit 3f49b30284
12 changed files with 208 additions and 8 deletions

View File

@ -2311,8 +2311,13 @@ Some situations can make haproxy return an HTTP error code to the client :
A succint error message taken from the RFC accompanies these return codes.
But depending on the clients knowledge, it may be better to return custom, user
friendly, error pages. This is made possible through the use of the 'errorloc'
command :
friendly, error pages. This is made possible in two ways, one involving a
redirection to a known server, and another one consisting in returning a local
file.
4.6.1) Relocation
-----------------
An error relocation is achieved using the 'errorloc' command :
errorloc <HTTP_code> <location>
@ -2344,6 +2349,33 @@ bring two new keywords to replace 'errorloc' : 'errorloc302' and 'errorloc303'.
They are preffered over errorloc (which still does 302). Consider using
errorloc303 everytime you know that your clients support HTTP 303 responses..
4.6.2) Local files
------------------
Sometimes, it is desirable to change the returned error without resorting to
redirections. The second method consists in loading local files during startup
and send them as pure HTTP content upon error. This is what the 'errorfile'
keyword does.
Warning, there are traps to consider :
- The files are loaded while parsing configuration, before doing a chroot().
Thus, they are relative to the real filesystem. For this reason, it is
recommended to pass an absolute path to those files.
- The contents of those files is not HTML, but real HTTP protocol with
possible HTML body. So the first line and headers are mandatory. Ideally,
every line in the HTTP part should end with CR-LF for maximum compatibility.
- The response is limited to the buffer size (BUSIZE), generally 8 or 16 kB.
- The response should not include references to the local server, in order to
avoid infinite loops on the browser in case of local failure.
Example :
---------
errorfile 400 /etc/haproxy/errorfiles/400badreq.http
errorfile 403 /etc/haproxy/errorfiles/403forbid.http
errorfile 503 /etc/haproxy/errorfiles/503sorry.http
4.7) Modifying default values
-----------------------------

View File

@ -2395,7 +2395,13 @@ Certaines situations conduisent
Un message d'erreur succint tiré de la RFC accompagne ces codes de retour.
Cependant, en fonction du type de clientèle, on peut préférer retourner des
pages personnalisées. Ceci est possible par le biais de la commande "errorloc":
pages personnalisées. Ceci est possible de deux manières, l'une reposant sur
une redirection vers un serveur connu, et l'autre consistant à retourner un
fichier local.
4.6.1) Redirection
------------------
Une redirection d'erreur est assurée par le biais de la commande "errorloc" :
errorloc <code_HTTP> <location>
@ -2431,6 +2437,38 @@ Leur usage non ambig
utilise toujours 302). Dans le doute, préférez l'utilisation de 'errorloc303'
dès que vous savez que vos clients supportent le code de retour HTTP 303.
4.6.2) Fichiers locaux
----------------------
Parfois il est souhaitable de changer l'erreur retournée sans recourir à des
redirections. La seconde méthode consiste à charger des fichiers locaux lors
du démarrage et à les envoyer en guise de pur contenu HTTP en cas d'erreur.
C'est ce que fait le mot clé 'errorfile'.
Attention, il y a des pièges à prendre en compte :
- les fichiers sont chargés durant l'analyse de la configuration, avant de
faire le chroot(). Donc ils sont relatifs au système de fichiers réel. Pour
cette raison, il est recommandé de toujours passer un chemin absolu vers ces
fichiers.
- le contenu de ces fichiers n'est pas du HTML mais vraiment du protocole HTTP
avec potentiellement un corps HTML. Donc la première ligne et les en-têtes
sont obligatoires. Idéalement, chaque ligne dans la partie HTTP devrait se
terminer par un CR-LF pour un maximum de compatibilité.
- les réponses sont limitées à une taille de buffer (BUFSIZE), généralement 8
ou 16 ko.
- les réponses ne devraient pas inclure de références aux serveurs locaux,
afin de ne pas risquer de créer des boucles infinies sur le navigateur dans
le cas d'une panne locale.
Exemple :
---------
errorfile 400 /etc/haproxy/errorfiles/400badreq.http
errorfile 403 /etc/haproxy/errorfiles/403forbid.http
errorfile 503 /etc/haproxy/errorfiles/503sorry.http
4.7) Changement des valeurs par défaut
--------------------------------------
Dans la version 1.1.22 est apparue la notion de valeurs par défaut, ce qui

View File

@ -0,0 +1,9 @@
HTTP/1.0 400 Bad request
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>400 Bad request</h1>
Your browser sent an invalid request.
</body></html>

View File

@ -0,0 +1,9 @@
HTTP/1.0 403 Forbidden
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>403 Forbidden</h1>
Request forbidden by administrative rules.
</body></html>

View File

@ -0,0 +1,9 @@
HTTP/1.0 408 Request Time-out
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>408 Request Time-out</h1>
Your browser didn't send a complete request in time.
</body></html>

View File

@ -0,0 +1,9 @@
HTTP/1.0 500 Server Error
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>500 Server Error</h1>
An internal server error occured.
</body></html>

View File

@ -0,0 +1,9 @@
HTTP/1.0 502 Bad Gateway
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>502 Bad Gateway</h1>
The server returned an invalid or incomplete response.
</body></html>

View File

@ -0,0 +1,9 @@
HTTP/1.0 503 Service Unavailable
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>503 Service Unavailable</h1>
No server is available to handle this request.
</body></html>

View File

@ -0,0 +1,9 @@
HTTP/1.0 504 Gateway Time-out
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>504 Gateway Time-out</h1>
The server didn't respond in time.
</body></html>

View File

@ -0,0 +1,9 @@
These files are default error files that can be customized
if necessary. They are complete HTTP responses, so that
everything is possible, including using redirects or setting
special headers.
They can be used with the 'errorfile' keyword like this :
errorfile 503 /etc/haproxy/errors/503.http

View File

@ -76,4 +76,5 @@ listen appli5-backup 0.0.0.0:10005
rspidel ^Set-cookie:\ IP= # do not let this cookie tell our internal IP address
errorloc 502 http://192.168.114.58/error502.html
errorfile 503 /etc/haproxy/errors/503.http

View File

@ -18,6 +18,10 @@
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <common/cfgparse.h>
#include <common/config.h>
@ -2093,11 +2097,6 @@ int cfg_parse_listen(const char *file, int linenum, char **args)
int errnum, errlen;
char *err;
// if (curproxy == &defproxy) {
// Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
// return -1;
// }
if (warnifnotcap(curproxy, PR_CAP_FE | PR_CAP_BE, file, linenum, args[0], NULL))
return 0;
@ -2131,6 +2130,64 @@ int cfg_parse_listen(const char *file, int linenum, char **args)
free(err);
}
}
else if (!strcmp(args[0], "errorfile")) { /* error message from a file */
int errnum, errlen, fd;
char *err;
struct stat stat;
if (warnifnotcap(curproxy, PR_CAP_FE | PR_CAP_BE, file, linenum, args[0], NULL))
return 0;
if (*(args[2]) == 0) {
Alert("parsing [%s:%d] : <%s> expects <status_code> and <file> as arguments.\n", file, linenum);
return -1;
}
fd = open(args[2], O_RDONLY);
if ((fd < 0) || (fstat(fd, &stat) < 0)) {
Alert("parsing [%s:%d] : error opening file <%s> for custom error message <%s>.\n",
file, linenum, args[2], args[1]);
if (fd >= 0)
close(fd);
return -1;
}
if (stat.st_size <= BUFSIZE) {
errlen = stat.st_size;
} else {
Warning("parsing [%s:%d] : custom error message file <%s> larger than %d bytes. Truncating.\n",
file, linenum, args[2], BUFSIZE);
errlen = BUFSIZE;
}
err = malloc(errlen); /* malloc() must succeed during parsing */
errnum = read(fd, err, errlen);
if (errnum != errlen) {
Alert("parsing [%s:%d] : error reading file <%s> for custom error message <%s>.\n",
file, linenum, args[2], args[1]);
close(fd);
free(err);
return -1;
}
close(fd);
errnum = atol(args[1]);
for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
if (http_err_codes[rc] == errnum) {
if (curproxy->errmsg[rc].str)
free(curproxy->errmsg[rc].str);
curproxy->errmsg[rc].str = err;
curproxy->errmsg[rc].len = errlen;
break;
}
}
if (rc >= HTTP_ERR_SIZE) {
Warning("parsing [%s:%d] : status code %d not handled, error customization will be ignored.\n",
file, linenum, errnum);
free(err);
}
}
else {
Alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "listen");
return -1;