Load a synthetic output template from a file

Tags: vcl (29)

In this tutorial we will override the VCL output template for vcl_synth and vcl_backend_error by loading it from a file.

These subroutines are used to compose the output when synthetic responses are returned. These are responses that do not originate from the origin server. When you trigger a synthetic response yourself, you end up in the vcl_synth subroutine. When the synthetic response is the result of a failed backend response, you end up in vcl_backend_error.

Built-in VCL for vcl_synth

Varnish’s built-in VCL uses the following template for synthetic output in vcl_synth:

sub vcl_synth {
    set resp.http.Content-Type = "text/html; charset=utf-8";
    set resp.http.Retry-After = "5";
    set resp.body = {"<!DOCTYPE html>
<html>
  <head>
    <title>"} + resp.status + " " + resp.reason + {"</title>
  </head>
  <body>
    <h1>Error "} + resp.status + " " + resp.reason + {"</h1>
    <p>"} + resp.reason + {"</p>
    <h3>Guru Meditation:</h3>
    <p>XID: "} + req.xid + {"</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>
"};
    return (deliver);
}

If you call return(synth(200,"Purge")); in your VCL code, the following HTML response will be generated:

<!DOCTYPE html>
<html>
  <head>
    <title>200 Purged</title>
  </head>
  <body>
    <h1>Error 200 Purged</h1>
    <p>Purged</p>
    <h3>Guru Meditation:</h3>
    <p>XID: 123456</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>

Built-in VCL for vcl_backend_error

While vcl_synth is manually triggered through return(synth(...)), vcl_backend_error is automatically called when Varnish fails to receive a valid response from the origin server.

This is the output template in the built-in VCL when backend errors occur:

sub vcl_backend_error {
    set beresp.http.Content-Type = "text/html; charset=utf-8";
    set beresp.http.Retry-After = "5";
    set beresp.body = {"<!DOCTYPE html>
<html>
  <head>
    <title>"} + beresp.status + " " + beresp.reason + {"</title>
  </head>
  <body>
    <h1>Error "} + beresp.status + " " + beresp.reason + {"</h1>
    <p>"} + beresp.reason + {"</p>
    <h3>Guru Meditation:</h3>
    <p>XID: "} + bereq.xid + {"</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>
"};
    return (deliver);
}

The output template is identical, but instead of using the resp object, the beresp object is used instead because the resp object is not available in the backend context.

Overriding the synthetic output template

It makes sense to override this template and change the aesthetics of the output by adding your own. Depending on the type of HTTP service you are using, you can return HTML, XML, JSON, plain text or any other kind of output.

Here’s a simple example where we use a plain text output template in vcl_synth:

sub vcl_synth {
    set resp.http.Content-Type = "text/plain; charset=utf-8";
    set resp.body = resp.reason + "(" + resp.status + ")";
    return (deliver);
}

And here’s the equivalent for vcl_backend_error:

sub vcl_backend_error {
    set beresp.http.Content-Type = "text/plain; charset=utf-8";
    set beresp.body = beresp.reason + "(" + beresp.status + ")";
    return (deliver);
}

Loading the synthetic output template form a file

If you’re planning to produce a lot of synthetic output, it makes sense to store the template in a separate file and load that file in VCL.

The std.fileread() function that is part of the std VMOD will load a file from disk and return the string value. This value is stored in resp.body and beresp.body.

However, we still need to inject the reason phrase into the output. In the example VCL code below, we do this by replacing the <<REASON>> placeholder with the resp.reason and beresp.reason values through the regsuball() function:

vcl 4.1;

import std;

sub vcl_synth {
    set resp.http.Content-Type = "text/html; charset=utf-8";
    set resp.http.Retry-After = "5";
    set resp.body = regsuball(std.fileread("/etc/varnish/synth.html"),"<<REASON>>",resp.reason);
    return (deliver);
}

sub vcl_backend_error {
    set beresp.http.Content-Type = "text/html; charset=utf-8";
    set beresp.http.Retry-After = "5";
    set beresp.body = regsuball(std.fileread("/etc/varnish/synth.html"),"<<REASON>>",beresp.reason);
    return (deliver);
}

This example assumes that the /etc/varnish/synth.html file contains a <<REASON>> placeholder. Here’s what this could look like:

<!DOCTYPE html>
<html>
  <head>
    <title>An error occured</title>
  </head>
  <body>
    <h1>An error occured</h1>
    <p><<REASON>></p>
  </body>
</html>

It is also possible to load multiple template files depending on the type of synthetic output you want to return.