Migrate from VCL

IMPORTANT: The content on this page is written for version 0.7.0 of the fastly crate. If you have previously used this example, your project may be using an older SDK version. View the changelog to learn how to migrate your program.

WARNING: This information is part of a limited availability release. Portions of this API may be subject to changes and improvements over time. Fields marked deprecated may be removed in the future and their use is discouraged. For more information, see our product and feature lifecycle descriptions.

Boilerplate

The examples below assume that you start with the default starter kit for your chosen language when building your application. This is the default when you run fastly compute init.

Naming conventions

These examples use a common set of naming conventions to draw parallels with VCL:

  • req: For the client request obtained from downstream_request() (Rust) or Fastly.getClientRequest() (AssemblyScript)
  • bereq: For a custom request built from scratch or by copying req
  • beresp: For the return value of a backend.send() (Rust) or Fastly.fetch() (AssemblyScript) call
  • resp: For a custom response built from scratch or by copying a beresp

Configuration

Load configuration data from a separate code file / snippet

  1. Fastly VCL
table settings {
"section.key": "some-value"
}
declare local var.some_value STRING;
set var.some_value = table.lookup(
settings,
"section.key"
);
  1. Rust
  2. AssemblyScript
// Using the config crate: https://docs.rs/config
use config::{Config, FileFormat};
let mut settings = Config::new();
settings
.merge(config::File::from_str(
include_str!("config.toml"), // assumes the existence of src/config.toml
FileFormat::Toml,
))
.unwrap();
let some_value = settings.get_str("section.key")?;

Load configuration data from an edge dictionary

  1. Fastly VCL
declare local var.some_value STRING;
set var.some_value = table.lookup(
example_dictionary,
"key_name"
);
  1. Rust
  2. AssemblyScript
let settings = Dictionary::open("example_dictionary");
let some_value = match settings.get("key_name") {
Some(value) => value,
_ => panic!("Value not set")
};

Requests

Add a header to a client request

  1. Fastly VCL
# constant value
set req.http.Host = "example.com";
# dynamic value
set req.http.Host = var.host_override;
  1. Rust
  2. AssemblyScript
// constant value
req.set_header("Host", "example.com");
// dynamic value
req.set_header("Host", host_override);

Sort and sanitize a query string

  1. Fastly VCL
set req.url = querystring.filter_except(req.url,
"a" + querystring.filtersep() +
"b" + querystring.filtersep() +
"c"
);
set req.url = querystring.sort(req.url);
  1. Rust
  2. AssemblyScript
let mut qs: Vec<(String, String)> = req.get_query()?;
qs.retain(|param| ["a", "b", "c"].contains(&param.0.as_str()));
qs.sort_by(|(a, _), (b, _)| a.cmp(b));
req.set_query(&qs)?;

Extract a single query string from the request

  1. Fastly VCL
declare local var.field STRING;
set var.field = subfield(req.url.qs, "paramName", "&");
  1. Rust
  2. AssemblyScript
// assuming a request http://example.com?paramName=someValue
let params: HashMap<String, String> = req.get_query()?;
assert_eq!(params["paramName"], "someValue");

Remove a header from a client request

  1. Fastly VCL
unset req.http.Some-Header;
  1. Rust
  2. AssemblyScript
req.remove_header("some-header");

Modify a request URL path

  1. Fastly VCL
set req.url = "/new/path" if(req.url.qs == "", "", "?") req.url.qs;
  1. Rust
req.set_path("/new/path");

Check for header presence on a client request

  1. Fastly VCL
if (req.http.Some-Header) {
# ... do something ...
}
  1. Rust
  2. AssemblyScript
if req.contains_header("some-header") {
// ... do something ...
}

Check whether a request header value contains a substring

  1. Fastly VCL
if (std.strstr(req.http.foo, "someValue")) {
# ... do something ...
}
  1. Rust
  2. AssemblyScript
fn header_val(header: Option<&HeaderValue>) -> &str {
match header {
Some(h) => h.to_str().unwrap_or(""),
None => "",
}
}
if header_val(req.get_header("some-header")).contains("someValue") {
// ... do something ...
}

Parse request cookies into key/value pairs

  1. Fastly VCL
# not achievable
  1. Rust
  2. AssemblyScript
fn header_val(header: Option<&HeaderValue>) -> &str {
match header {
Some(h) => h.to_str().unwrap_or(""),
None => "",
}
}
let cookie_val: &str = header_val(req.get_header("cookie"));
let parsed_cookie_val: HashMap<String, String> = cookie_val
.split(";")
.filter_map(|kv| {
kv.find("=").map(|index| {
let (key, value) = kv.split_at(index);
let key = key.trim().to_string();
let value = value[1..].to_string();
(key, value)
})
})
.collect();

Extract constituent parts of a request

  1. Fastly VCL
# not achievable
  1. Rust
  2. AssemblyScript
let method = req.get_method();
let url = req.get_url();
let my_header = req.get_header("my-header");
let version = req.get_version();

Build a request from scratch

  1. Fastly VCL
# not achievable
  1. Rust
  2. AssemblyScript
let req = Request::post("https://example.com/").with_header("some-header", "someValue");

Identify a client's geolocation information

  1. Fastly VCL
declare local var.country_code STRING;
declare local var.country_name STRING;
declare local var.city STRING;
set var.country_code = client.geo.country_code;
set var.country_name = client.geo.country_name;
set var.city = client.geo.city;
  1. Rust
  2. AssemblyScript
let client_ip = req.get_client_ip_addr().unwrap();
let geo = geo_lookup(client_ip).unwrap();
let country_code = geo.country_code();
let country_name = geo.country_name();
let city_name = geo.city();

Backends

Get a handle to a backend

  1. Fastly VCL
F_example_backend;
  1. Rust
  2. AssemblyScript
let backend = Backend::from_name("example_backend")?;

Send a client request to a backend

  1. Fastly VCL
# default behavior
  1. Rust
  2. AssemblyScript
let beresp = req.send("example_backend")?;

Route requests to backends based on URL path match

  1. Fastly VCL
if (req.url.path == "/") {
set req.backend = F_origin_0;
} else if (req.url.path ~ "^/other/") {
set req.backend = F_origin_1;
} else {
set req.backend = F_origin_2;
}
  1. Rust
  2. AssemblyScript
match (req.get_method(), req.get_path()) {
(&Method::GET, "/") => Ok(req.send("backend_one")?),
(&Method::GET, path) if path.starts_with("/other/") => Ok(req.send("backend_two")?),
_ => Ok(req.send("default_backend")?),
}

Retry a request on error, using a different backend

  1. Fastly VCL
# not achievable for POST requests
# because after a restart the body of a POST request will not be preserved
sub vcl_recv {
set req.backend = F_Host_1;
if (req.restarts == 1) {
set req.backend = F_Host_2;
}
}
sub vcl_fetch {
if (req.restarts == 0 && beresp.status >= 500 && beresp.status < 600 && (req.method == "GET" or req.method == "HEAD")) {
restart;
}
}
  1. Rust
  2. AssemblyScript
let body_bytes = req.take_body_bytes();
req.set_body(body_bytes.as_slice());
let mut beresp = req.send("backend_one")?;
if beresp.get_status().is_server_error() {
let mut retry_req = beresp.take_backend_request().unwrap();
retry_req.set_body(body_bytes);
let beresp_retry = retry_req.send("backend_two")?;
if !beresp_retry.get_status().is_server_error() {
return Ok(beresp_retry);
}
}
Ok(beresp)

Responses

Build a response from scratch

  1. Fastly VCL
synthetic "Hello world";
  1. Rust
  2. AssemblyScript
let res = Response::from_body("Hello world");

Build an image response

  1. Fastly VCL
synthetic.base64 "R0lGODlh...=";
  1. Rust
let res = Response::from_body(include_bytes!("fastly.jpg").as_ref())
.with_content_type(mime::IMAGE_JPEG)
.with_header("cache-control", "private, no-store");

Build a response from the parts of an existing response

  1. Fastly VCL
# not achievable
  1. Rust
  2. AssemblyScript
let mut beresp = req.send("example_backend")?;
let beresp_body = beresp.take_body();
beresp.set_body("The original response body follows:\n");
beresp.append_body(beresp_body);

Add a header to a response

  1. Fastly VCL
# constant value
set resp.http.Some-Header = "someValue";
# dynamic value
set resp.http.Set-Cookie = "origin-session=" + var.session + "; HttpOnly";
  1. Rust
  2. AssemblyScript
// constant value
let mut res = req.send("example_backend")?;
res.set_header("some-header", "bar");
// dynamic value
let session: String = some_function();
res.set_header("set-cookie", format!("origin-session={}; HttpOnly", session));

Template a response (HTML, containing JavaScript code)

It's better not to generate code by concatenating strings. With Compute@Edge, you can take advantage of available templating libraries to generate HTML code at the edge.

  1. Fastly VCL
declare local var.ga_id STRING;
set var.ga_id = "UA-12345678-00";
synthetic {"<script async src="https://www.googletagmanager.com/gtag/js?id="} var.ga_id {""></script>
<script>window.dataLayer=window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag("js", new Date()); gtag("config", ""} var.ga_id {"");</script>"};
  1. Rust
// Here we are using the horrorshow crate to create valid HTML
// https://docs.rs/horrorshow
use horrorshow::{prelude, html};
let analytics_snippet = format!(
"{}",
// insert the Google Analytics ID into the `<script>` tag as a `data-` attribute
// and then access it from inside the inline script using `document.currentScript`
html! {
script(async, src=format!("https://www.googletagmanager.com/gtag/js?id={}", GA_ID));
script(data-ga=GA_ID) {
: prelude::Raw(r#"window.dataLayer=window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag("js", new Date()); gtag("config", document.currentScript.dataset.ga);"#)
}
}
);
Ok(Response::from_body(analytics_snippet))

Controlling the cache

Explicitly set a TTL

  1. Fastly VCL
set beresp.ttl = 60s;
  1. Rust
  2. AssemblyScript
// You can use individual helper methods, like `set_ttl`,
// to modify individual cache override settings.
req.set_ttl(60);

Force a pass

  1. Fastly VCL
return(pass);
  1. Rust
  2. AssemblyScript
// drop all overrides and force pass
req.set_pass(true);

Explicitly set stale-while-revalidate

  1. Fastly VCL
set beresp.stale_while_revalidate = 60s;
  1. Rust
  2. AssemblyScript
req.set_stale_while_revalidate(60);