Redirects

Your servers often have to handle millions of requests for old and non-canonical URLs. This can cause unneeded load, as well as make logs messier and, if you have recently changed your site's URL scheme, you might be redirecting a lot! Learn how to shift all your static redirects to the edge using an Edge dictionary.

Illustration of pattern concept

Instructions

Create a dataset of redirects as a VCL table

Redirects can be based on complex patterns or exact URL path matches. The latter allows us to use a VCL table key-value store, for a fast lookup and minimal code. Start by creating a VCL table:

sub vcl_init { ... }
Fastly VCL
table solution_redirects {
"/source1": "/dest1",
"/source2": "/dest2"
}

HINT: It is possible to update these values via an HTTP API call, using an Edge dictionary, which is exposed in VCL in the same way as the table above.

Match inbound requests against redirect table

Now you have created a data structure containing the source and destination URLs, the next step is to identify inbound requests that match one of your redirection patterns. The req.url variable exposes the portion of the requested URL following the hostname, which may include a query string. For redirection purposes the query string usually doesn't matter, so we'll use req.url.path, which extracts just the path portion. If you want to match the query as well, strip off the .path suffix.

sub vcl_recv { ... }
Fastly VCL
if (table.lookup(solution_redirects, req.url.path)) {
error 618 "redirect";
}

Now, when a request matches one of your source paths, it will trigger an error. The error statement moves the control flow from the RECV event to the ERROR event, and creates a synthetic object that we can manipulate. To allow us to distinguish this special kind of error from any other that might end up triggering the ERROR event, we pass a unique HTTP status code and response status text. HTTP status codes below 600 are reserved by the specification, so we recommend using something in the 600-699 range. All solution patterns in our library use 618 and distinguish themselves with a unique status text (in this case, 'redirect').

Generate the redirection response

The request moves to the ERROR event, where we can now capture it and modify the synthetic object:

sub vcl_error { ... }
Fastly VCL
if (obj.status == 618 && obj.response == "redirect") {
set obj.status = 308;
set obj.http.Location = "https://" + req.http.host + table.lookup(solution_redirects, req.url.path) + if (std.strlen(req.url.qs) > 0, "?" req.url.qs, "");
return (deliver);
}

We convert the synthetic object from an 618 to a 308 response code, and construct an appropriate Location header for the redirect. Let's break down what we include in this:

  • "https://" hard codes the protocol - why not use the opportunity to normalize the request to HTTPS? But if you do want to match the inbound protocol, check out req.proto.
  • req.http.host matches the inbound host. Fastly services can have many domains associated with them, so you probably want to redirect within the same domain. If you want to normalize, feel free to hard code here.
  • table.lookup retrieves the destination path. Since no scope is maintained between the RECV and ERROR events (in fact we may execute them on different physical machines) we need to perform the lookup again. Don't worry, it's very fast.
  • req.url.qs appends the querystring from the inbound request. You may want to consider leaving this out if you don't want to preserve query strings through the redirect.

Setup a test request

Before we test this by running the fiddle, you will want to set the request path for the test request to something that is in your dictionary of redirects:

/source1

Now run the fiddle. You should get a 301 response.

Next steps

This solution can only match entire paths (with or without querystring). To match URL patterns using regular expressions, you will need to write your redirect conditions as a series of if statements. As a compromise that would still allow you to use a table (as in the solution described on this page), you could treat each of the source paths as a prefix rather than a full URL (check out the std.strstr function for that).

This solution contains a VCL table. If you prefer, you can define the table using an Edge dictionary, which enables you to manage the table via HTTP API calls without having to clone and activate new versions of your service.

See also

VCL Reference:

Blog posts:

Quick install

This solution can be added directly to an existing service in a Fastly account as a set of VCL snippets. The embedded fiddle below shows the complete solution. Feel free to run it, and click the INSTALL tab to customise and upload it to your service:

Once you have the code in your service, you can further customise it if you need to.

All code on this page is provided under both the BSD and MIT open source licenses.