Blog Tenya.me

some blog : )

Varnish 3.x custom error pages

| Comments

Most VCL examples and Varnish wiki offers two solutions for custom error pages. First one is a strict HTML code in a synthetic block and second one is a reading html file from disk with inline C. Both variants work, but makes our VCL code nasty (also second variant with inline C reads file from disk on every ‘wrong’ request).

I want to suggest third variant, that combines both previous variants without their bad side.

Before I tell you about the my method, I want to describe first two methods.

Strict HTML method:
default.vcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sub vcl_error {
  if (obj.status >= 500 && obj.status <= 505) {
      set obj.http.Content-Type = "text/html; charset=utf-8";
      synthetic {"
         <html>
             <head>
                 <title>Error happened</title>
             </head>
             <body>
                 <h1>Error description</h1>
             </body>
         </html>
     "};
  }
    return (deliver);
}

If we need to have different error pages(with diffrent descriptions, etc) for varios error codes, then we need to duplicate this snippet for each error code or error codes range.

Inline C method:
default.vcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
C{
  #include <stdio.h>
  #include <string.h>
}C

sub vcl_error {
  if ( obj.status >= 500 && obj.status <= 505) {
     set obj.http.Content-Type = "text/html; charset=utf-8";
           C{
      FILE * pFile;
      char content [100];
      char page [10240];
                char fname [50];

      page[0] = '\0';

      pFile = fopen("/var/www/errors/50x.html", "r");
      while (fgets(content, 100, pFile)) {
        strcat(page, content);
      }
      fclose(pFile);
      VRT_synth_page(sp, 0, page, "<!-- XID: ", VRT_r_req_xid(sp), " -->", vrt_magic_string_end);
             }C
       return(deliver);
  }
  else {
          set obj.http.Content-Type = "text/html; charset=utf-8";
      synthetic {"
     <?xml version="1.0" encoding="utf-8"?>
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
     <html>
     <head>
     <title>"} + obj.status + " " + obj.response + {"</title>
     </head>
     <body>
     <h1>Error "} + obj.status + " " + obj.response + {"</h1>
     <p>"} + obj.response + {"</p>
     <h3>Guru Meditation:</h3>
     <p>XID: "} + req.xid + {"</p>
     <address>
     <a href=" + http://www.varnish-cache.org/ + ">Varnish</a>
     </address>
     </body>
     </html>
     "};
      return(deliver);
  }
}

Duplication will increase size of our unclean code and complicate the understanding, as in previous strict HTML variant. As I already said, file will be read on each error request. Don’t use inline C in production, if you don’t know what you’re doing.

So, my variant is a combination: read file from disk to memory.

Varnish 3.x comes with Standard VMOD, that contains fileread function. To load and use it in our VCL we need to declare standard module in begginning of VCL file:

default.vcl
1
import std;

For example I will take 50x error page from nginx distribution and place it to /var/www/errors/50x.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html>
<head>
<title>The page is temporarily unavailable</title>
<style>
body { font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body bgcolor="white" text="black">
<table width="100%" height="100%">
<tr>
<td align="center" valign="middle">
The page you are looking for is temporarily unavailable.<br/>
Please try again later.
</td>
</tr>
</table>
</body>
</html>

And to read it to memory in Varnish I will use next snippet:

default.vcl
1
2
3
4
5
6
7
sub vcl_error {
  if ( obj.status >= 500 && obj.status <= 505) {
      set obj.http.Content-Type = "text/html; charset=utf-8";
        synthetic std.fileread("/var/www/errors/50x.html");;
        return(deliver);
  }
}

2014/04/24 UPDATE:

Varnish 4 changes: synthetic responses has been moved from vcl_error to vcl_synth sub-routine

So, last construction will look as:

default.vcl
1
2
3
4
5
6
7
sub vcl_synth {
  if (resp.status >= 500 && resp.status <= 505) {
      set resp.http.Content-Type = "text/html; charset=utf-8";
      synthetic(std.fileread("/var/www/errors/50x.html"));
      return(deliver);
  }
}

Comments