Comparing Compute and VCL
If you already have a Fastly CDN service with custom VCL but want to switch to a Compute service, all the logic you wrote in VCL can be accomplished in Compute in any supported language. This page provides the equivalent Compute service code for the most common patterns we see in VCL.
HINT: Switching from a VCL to Compute service is not necessarily the right thing for you. VCL is fully supported and remains the native, active standard for CDN Services.
This guide is intended as a quick-start on the Compute platform, not a comprehensive library of code examples. If the pattern you are trying to use is not here or you are looking to do something more complex, you might find a code example in our examples library.
For more information about each of the languages with official SDK support, see choosing a language.
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 incoming client requestbeReq: For a custom request built from scratch or by copyingreqbeResp: For the return value of an origin fetchresp: For a custom response built from scratch or by copying abeResp
Configuration
Load configuration data from a separate file
- Fastly VCL
123456789table settings { "section.key": "some-value"}
declare local var.some_value STRING;set var.some_value = table.lookup( settings, "section.key");- Rust
- JavaScript
- Go
1234567891011121314// Using the config crate: https://docs.rs/configuse config::{Config, FileFormat};use fastly::{Error, Request, Response};
#[fastly::main]fn main(_req: Request) -> Result<Response, Error> { let config_builder = Config::builder().add_source(config::File::from_str( include_str!("config.toml"), // assumes the existence of src/config.toml FileFormat::Toml, )); let settings = config_builder.build()?; let some_value = settings.get_string("section.key")?; Ok(Response::from_body(some_value.as_str()))}25678 import { responseText } from "./config"; // Create a response with the body set to a value loaded from the config file. return new Response(responseText, { headers: { "Content-Type": "text/plain" } }); export const responseText = "hello world";12131718192021 //go:embed config.tomlvar config []byte resp := fsthttp.Response{ StatusCode: fsthttp.StatusOK, // Create a response with the body set to a value loaded from the config file. Body: io.NopCloser(bytes.NewReader(config)), } Load configuration data from a dictionary
- Fastly VCL
declare local var.some_value STRING;set var.some_value = table.lookup( example_dictionary, "key_name");- Rust
- JavaScript
- Go
1234567891011use fastly::{Error, Request, Response, ConfigStore};
#[fastly::main]fn main(_req: Request) -> Result<Response, Error> { let settings = ConfigStore::open("example_config_store"); let some_value = match settings.get("key_name") { Some(value) => value, _ => panic!("Value not set") }; Ok(Response::from_body(some_value))}56 let exampleStore = new ConfigStore("example_config_store"); let someValue = exampleStore.get("key_name"); 1320 d, err := configstore.Open("example_config_store") v, err := d.Get("key_name") Requests
Add a header to a client request
- Fastly VCL
# constant valueset req.http.Accept-Encoding = "br";# dynamic valueset req.http.Accept-Encoding = var.accept_encoding;- Rust
- JavaScript
- Go
1234567891011use fastly::{Error, Request, Response};
#[fastly::main]fn main(mut req: Request) -> Result<Response, Error> { let accept_encoding = "br"; // constant value req.set_header("Accept-Encoding", "br"); // dynamic value req.set_header("Accept-Encoding", accept_encoding); Ok(req.send("example_backend")?)}789101112 // Constant value. req.headers.set("Accept-Encoding", "example.com");
// Dynamic value. let accept_encoding = "br"; req.headers.set("Accept-Encoding", accept_encoding); 27282930 // constant value req.Header.Add("Accept-Encoding", "br") // dynamic value req.Header.Add("Accept-Encoding", acceptEncoding) Sort and sanitize a query string
- Fastly VCL
set req.url = querystring.filter_except(req.url, "a" + querystring.filtersep() + "b" + querystring.filtersep() + "c");set req.url = querystring.sort(req.url);- Rust
- JavaScript
- Go
12345678910use fastly::{Error, Request, Response};
#[fastly::main]fn main(mut req: Request) -> Result<Response, Error> { let mut qs: Vec<(String, String)> = req.get_query()?; qs.retain(|param| ["a", "b", "c"].contains(¶m.0.as_str())); qs.sort_by(|(a, _), (b, _)| a.cmp(b)); req.set_query(&qs)?; Ok(req.send("example_backend")?)}456789101112131415161718 const req = event.request; const url = new URL(req.url);
const ALLOWED = [ 'a', 'b', 'c' ]; const searchEntries = url.searchParams.entries(); const filteredEntries = searchEntries.filter(([k,v]) => ALLOWED.includes(k));
const filteredParams = new URLSearchParams(filteredEntries); filteredParams.sort(); url.search = filteredParams;
// Create a new Request object with a sorted URL. const newReq = new Request(url, req);
return fetch(newReq, { backend: "origin_0" }); 192021222327282930313233 allowed := map[string]bool{ "a": true, "b": true, "c": true, } qs := r.URL.Query() for k := range qs { if _, ok := allowed[k]; !ok { qs.Del(k) } } r.URL.RawQuery = qs.Encode() Extract a query string parameter from the request
- Fastly VCL
declare local var.field STRING;set var.field = subfield(req.url.qs, "paramName", "&");- Rust
- JavaScript
- Go
12345678910use fastly::{Error, Request, Response};use std::collections::HashMap;
#[fastly::main]fn main(req: Request) -> Result<Response, Error> { // assuming a request http://example.com?paramName=someValue let params: HashMap<String, String> = req.get_query()?; assert_eq!(params["paramName"], "someValue"); Ok(req.send("example_backend")?)}4567 const req = event.request; const url = new URL(req.url);
const val = url.searchParams.get("myParam") ?? "myParam is not set"; 13141516171819202122 param := "paramName" v := param + " is not set" if p := r.URL.Query().Get(param); p != "" { v = p }
resp := fsthttp.Response{ StatusCode: fsthttp.StatusOK, Body: io.NopCloser(strings.NewReader(v)), } Remove a header from a client request
- Fastly VCL
unset req.http.Some-Header;- Rust
- JavaScript
- Go
use fastly::{Error, Request, Response};
#[fastly::main]fn main(mut req: Request) -> Result<Response, Error> { req.remove_header("some-header"); Ok(req.send("example_backend")?)}6 req.headers.delete("some-header"); 16 r.Header.Del("Some-Header") Modify a request URL path
- Fastly VCL
set req.url = "/new/path" + if(req.url.qs == "", "", "?") + req.url.qs;- Rust
- JavaScript
- Go
use fastly::{Error, Request, Response};
#[fastly::main]fn main(mut req: Request) -> Result<Response, Error> { req.set_path("/new/path"); Ok(req.send("example_backend")?)}4567891011121314 const req = event.request; const url = new URL(req.url);
url.pathname = "/new/path";
// Create a new Request object with an updated URL. const newReq = new Request(url, req);
return fetch(newReq, { backend: "example_backend", }); 16 r.URL.Path = "/new/path" Check for header presence on a client request
- Fastly VCL
if (req.http.Some-Header) { # ... do something ...}- Rust
- JavaScript
- Go
123456789use fastly::{Error, Request, Response};
#[fastly::main]fn main(req: Request) -> Result<Response, Error> { if req.contains_header("some-header") { // ... do something ... } Ok(req.send("example_backend")?)}678 if (req.headers.has("some-header")) { // ... do something ... } 161718 if h := r.Header.Get("Some-Header"); h != "" { // ... do something ... } Check whether a request header value contains a substring
- Fastly VCL
if (std.strstr(req.http.foo, "someValue")) { # ... do something ...}- Rust
- JavaScript
- Go
1234567891011121314151617use fastly::http::HeaderValue;use fastly::{Error, Request, Response};
fn header_val(header: Option<&HeaderValue>) -> &str { match header { Some(h) => h.to_str().unwrap_or(""), None => "", }}
#[fastly::main]fn main(req: Request) -> Result<Response, Error> { if header_val(req.get_header("some-header")).contains("someValue") { // ... do something ... } Ok(req.send("example_backend")?)}678 if (req.headers.get("some-header")?.includes("someValue")) { // ... do something ... } 171819 if h := r.Header.Get("Some-Header"); strings.Contains(h, "someValue") { // ... do something ... } Extract constituent parts of a request
- Fastly VCL
123456789declare local var.req_method STRING;declare local var.req_url STRING;declare local var.req_header STRING;declare local var.req_protocol STRING;set var.req_method = req.method;set var.req_url = req.url;set var.req_header = req.http.My-Header;set var.req_body = req.body;set var.req_protocol = if (fastly_info.is_h2, "2", "1.1");- Rust
- JavaScript
- Go
12345678910use fastly::{Error, Request, Response};
#[fastly::main]fn main(req: Request) -> Result<Response, Error> { let method = req.get_method(); let url = req.get_url(); let my_header = req.get_header("my-header"); let version = req.get_version(); Ok(req.send("example_backend")?)}45678910 // Get the request from the client. const req = event.request;
const method = req.method; const url = new URL(req.url); const headers = req.headers; const body = await req.text(); 161718192021 fmt.Printf(` method: %s url: %s headers: %v protocol: %s `, r.Method, r.URL.String(), r.Header.Keys(), r.Proto) Identify a client's geolocation information
- 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;- Rust
- JavaScript
- Go
123456789101112use fastly::geo::geo_lookup;use fastly::{Error, Request, Response};
#[fastly::main]fn main(req: Request) -> Result<Response, Error> { 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(); Ok(req.send("example_backend")?)}45678 const clientGeo = event.client.geo;
const countryCode = clientGeo.country_code; const countryName = clientGeo.country_name; const city = clientGeo.city; 1415161718192021222324 ip := net.ParseIP(r.RemoteAddr) g, err := geo.Lookup(ip) if err != nil { w.WriteHeader(fsthttp.StatusInternalServerError) fmt.Fprintln(w, err.Error()) return }
fmt.Fprintf(w, "CountryCode: %q\n", g.CountryCode) fmt.Fprintf(w, "CountryCode3: %q\n", g.CountryCode3) fmt.Fprintf(w, "CountryName: %q\n", g.CountryName) Send data to a log endpoint
- Fastly VCL
log "syslog " + req.service_id + " request_logger :: " + now.sec + " " + client.ip + " " + req.url + " " + req.http.user-agent;- Rust
- JavaScript
- Go
12345678910111213141516171819202122232425262728293031use fastly::http::StatusCode;use fastly::{compute_runtime, Error, Request, Response};use std::time::{SystemTime, UNIX_EPOCH};
#[fastly::main]fn main(req: Request) -> Result<Response, Error> { log_fastly::init_simple("request_logger", log::LevelFilter::Info);
let service_id = compute_runtime::service_id();
let since_epoch = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
let client_ip = req .get_client_ip_addr() .map(|ip| ip.to_string()) .unwrap_or_else(String::new);
let req_url = req.get_url_str();
let user_agent = req .get_header("USER_AGENT") .map(|header| header.to_str()) .transpose()? .unwrap_or("");
log::info!( "fastly_service_id: {service_id}, since_epoch: {since_epoch}, client_ip: {client_ip}, request_url: {req_url}, user_agent: {user_agent}" );
Ok(Response::from_status(StatusCode::OK).with_body("Welcome to Fastly Compute"))}[dependencies] fastly = "^0.11.0" log-fastly = "^0.11.0" log = "^0.4.17"789101112131415 const logger = new Logger("log_endpoint_name");
let service_id = event.request.headers.get("FASTLY_SERVICE_ID"); let since_epoch = Math.floor( new Date() / 1000 ); let client_ip = event.client.address; let req_url = event.request.url; let user_agent = event.request.headers.get("USER_AGENT");
logger.log("fastly_service_id: " + service_id + ", since_epoch: " + since_epoch + ", client_ip: " + client_ip + ", request_url: " + req_url + ", user_agent: " + user_agent); 151617 endpoint := rtlog.Open("request_logger") msg := "fastly_service_id: %s, since_epoch: %d, client_ip: %s, request_url: %s, user_agent: %s\n" fmt.Fprintf(endpoint, msg, os.Getenv("FASTLY_SERVICE_ID"), time.Now().UnixMilli(), r.RemoteAddr, r.URL, r.Header.Get("User-Agent")) Backends
Route requests to backends based on URL path match
- 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;}- Rust
- JavaScript
- Go
1234567891011use fastly::http::Method;use fastly::{Error, Request, Response};
#[fastly::main]fn main(req: Request) -> Result<Response, Error> { 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")?), }}4567891011121314151617 const req = event.request; const url = new URL(req.url);
let backendName = "default_backend";
if (url.pathname === "/") { backendName = "backend_one"; } else if (url.pathname.startsWith("/other/")) { backendName = "backend_two"; }
return fetch(clientRequest, { backend: backendName, }); 1314151621222324252627 // Backend1 is a service backend pointing at httpbin.org. Backend1 = "httpbin" // Backend2 is a service backend pointing at example.org. Backend2 = "example" backend := Backend1
if r.URL.Path == "/about/" || strings.HasPrefix(r.URL.Path, "/other/") { backend = Backend2 }
resp, err := r.Send(ctx, backend) Retry a request on error, using a different backend
- Fastly VCL
1234567891011121314# not achievable for POST requests# because after a restart the body of a POST request will not be preservedsub 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; }}- Rust
- JavaScript
- Go
123456789101112131415161718use fastly::{Error, Request, Response};
#[fastly::main]fn main(mut req: Request) -> Result<Response, Error> { 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)}4567891011121314151617181920212223 const req = event.request;
const url = req.url; const method = req.method; const headers = req.headers; const body = ['GET', 'HEAD'].includes(req.method) ? await req.arrayBuffer() : undefined;
const beReq = new Request(url, { method, headers, body }); let beResp = await fetch(beReq, { backend: "backend_one" });
if (beResp.status >= 500 && beResp.status < 600) { const beReq2 = new Request(url, { method, headers, body }); beResp = await fetch(beReq2, { backend: "backend_two" }); }
return beResp; 121314152324252627282930313233343536 // Backend1 is a service backend pointing at httpbin.org. Backend1 = "httpbin" // Backend2 is a service backend pointing at example.org. Backend2 = "example" backend := Backend1
resp, err := r.Send(ctx, backend) if err != nil || is5xx(resp.StatusCode) { backend = Backend2 r = r.Clone()
resp, err = r.Send(ctx, backend) if err != nil { w.WriteHeader(fsthttp.StatusBadGateway) fmt.Fprintln(w, err.Error()) return } } Responses
Build a response from scratch
- Fastly VCL
set obj.status = 200;synthetic "Hello world";return(deliver);- Rust
- JavaScript
- Go
use fastly::{Error, Request, Response};
#[fastly::main]fn main(_req: Request) -> Result<Response, Error> { let res = Response::from_body("Hello world"); Ok(res)}6789 return new Response("Hello world", { status: 200, headers: { "Content-Type": "text/plain" } }); 13141516 resp := fsthttp.Response{ StatusCode: fsthttp.StatusOK, Body: io.NopCloser(strings.NewReader("Hello, world!")), } Build an image response
- Fastly VCL
synthetic.base64 "R0lGODlh...=";- Rust
- JavaScript
- Go
123456789use fastly::{mime, Error, Request, Response};
#[fastly::main]fn main(_req: Request) -> Result<Response, Error> { let res = Response::from_body(include_bytes!("fastly.jpg").as_ref()) .with_content_type(mime::IMAGE_JPEG) .with_header("cache-control", "private, no-store"); Ok(res)}12345678910111213/// <reference types="@fastly/js-compute" />import { includeBytes } from "fastly:experimental";
const IMAGE = includeBytes('src/fastly.jpg');
async function handleRequest(_event) { return new Response(IMAGE, { status: 200, headers: { 'content-type': 'image/jpg' } });}
addEventListener('fetch', event => event.respondWith(handleRequest(event)));12131718192021222324 //go:embed fastly.jpgvar image []byte h := make(fsthttp.Header) h.Set("Content-Type", "image/jpeg")
resp := fsthttp.Response{ StatusCode: fsthttp.StatusOK, Header: h, Body: io.NopCloser(bytes.NewReader(image)), } Add a header to a response
- Fastly VCL
# constant valueset resp.http.Some-Header = "someValue";# dynamic valueset resp.http.Set-Cookie = "origin-session=" + var.session + "; HttpOnly";- Rust
- JavaScript
- Go
123456789101112use fastly::{Error, Request, Response};
#[fastly::main]fn main(req: Request) -> Result<Response, Error> { // constant value let mut res = req.send("example_backend")?; res.set_header("some-header", "bar"); // dynamic value let session = String::from("some-session-id"); res.set_header("set-cookie", format!("origin-session={}; HttpOnly", session)); Ok(res)}1314 beResp.headers.set('Some-Header', 'someValue'); beResp.headers.set('Set-Cookie', "origin-session=" + session + "; HttpOnly"); 3435 resp.Header.Add("Some-Header", "example") resp.Header.Add("Set-Cookie", fmt.Sprintf("origin-session=%s; HttpOnly", session)) Controlling the cache
Explicitly set a TTL
- Fastly VCL
set beresp.ttl = 60s;- Rust
- JavaScript
- Go
12345678910use fastly::{Error, Request, Response};
#[fastly::main]fn main(mut req: Request) -> Result<Response, Error> { // You can use individual helper methods, like `set_ttl`, // to modify individual cache override settings. req.set_ttl(60);
Ok(req.send("example_backend")?)}6789101112 // Create a cache override. const cacheOverride = new CacheOverride({ ttl: 60 });
return fetch(req, { backend: "example_backend", cacheOverride }); 16 r.CacheOptions.TTL = 60 Force a pass
- Fastly VCL
return(pass);- Rust
- JavaScript
- Go
12345678use fastly::{Error, Request, Response};
#[fastly::main]fn main(mut req: Request) -> Result<Response, Error> { // drop all overrides and force pass req.set_pass(true); Ok(req.send("example_backend")?)}67891011 let cacheOverride = new CacheOverride("pass");
return fetch(req, { backend: "example_backend", cacheOverride }); 161718192021 // Determine the framing headers (Content-Length/Transfer-Encoding) // based on the message body (default) r.ManualFramingMode = false
// Make sure the response isn't cached. r.CacheOptions.Pass = true Explicitly set stale-while-revalidate
- Fastly VCL
set beresp.stale_while_revalidate = 60s;- Rust
- JavaScript
- Go
use fastly::{Error, Request, Response};
#[fastly::main]fn main(mut req: Request) -> Result<Response, Error> { req.set_stale_while_revalidate(60); Ok(req.send("example_backend")?)}67891011 let cacheOverride = new CacheOverride({ swr: 60 });
return fetch(req, { backend: "example_backend", cacheOverride }); 16 r.CacheOptions.StaleWhileRevalidate = 60