Andrew Betts Web Developer, The Financial Times at Fastly Altitude 2016
Running custom code at the Edge using a standard language is one of the biggest advantages of working with Fastly’s CDN. Andrew gives you a tour of all the problems the Financial Times and Nikkei solve in VCL and how their solutions work.
2. Who is this guy?
1. Helped build the original HTML5 web
app for the FT
2. Created our Origami component
system
3. Ran FT Labs for 3 years
4. Now working with Nikkei to rebuild
nikkei.com
5. Also W3C Technical Architecture
Group
6. Live in Tokyo, Japan
2
Pic of me.
10. Benefits of edge code
10
1. Smarter routing
2. Faster authentication
3. Bandwidth management
4. Higher cache hit ratio
11. Edge side includes
11
<esi:include src="http://example.com/1.html"
alt="http://bak.example.com/2.html" onerror="continue"/>
index.html
my-news.html
Cache-control: max-age=86400
Cache-control: private
Server
12. The VCL way
1. Request and response bodies are opaque
2. Everything happens in metadata
3. Very restricted: No loops or variables
4. Extensible: some useful Fastly extensions include geo-ip and crypto
5. Incredibly powerful when used creatively
12
13. SOA Routing
Send requests to multiple
microservice backends
This is great if...
You have a microservice architecture
Many backends, one domain
You add/remove services regularly
1
14. SOA Routing in VCL
14
Front page
Article page
Timeline
Content API
Choose a backend
based on a path match
of the request URL
/article/123
15. SOA Routing in VCL
15
[
{
name,
paths,
host,
useSsl,
}, …
]
{{#each backends}}
backend {{name}} {
.port = "{{p}}";
.host = "{{h}}";
}
{{/each}}
let vclContent =
vclTemplate(data);
fs.writeFileSync(
vclFilePath,
vclContent,
'UTF-8'
);
services.json
Defines all the backends
and paths that they
control.
routing.vcl.handlebars
VCL template with
Handlebars placeholders
for backends & routing
build.js
Task script to merge
service data into VCL
template
16. SOA Routing: key tools and techniques
● Choose a backend:
set req.backend = {{backendName}};
● Match a route pattern:
if (req.url ~ "{{pattern}}")
● Remember to set a Host header:
set req.http.Host = "{{backendhost}}";
● Upload to Fastly using FT Fastly tools
○ https://github.com/Financial-Times/fastly-tools
16
20. UA Targeting
Return user-agent specific
responses without destroying
your cache hit ratio
This is great if...
You have a response that is tailored
to different device types
There are a virtually infinite number of
User-Agent values
2
23. UA targeting: key tools and techniques
● Remember something using request headers:
set req.http.tmpOrigURL = req.url;
● Change the URL of the backend request:
set req.url = "/api/normalizeUA?ua=" req.http.User-Agent;
● Reconstruct original URL adding a backend response header:
set req.url = req.http.tmpOrigURL "?ua=" resp.http.NormUA;
● Restart to send the request back to vcl_recv:
restart;
23
24. ua-targeting.vcl
24
sub vcl_recv {
if (req.url ~ "^/v2/polyfill." && req.url
!~ "[?&]ua=") {
set req.http.X-Orig-URL = req.url;
set req.url = "/v2/normalizeUa?ua="
urlencode(req.http.User-Agent);
}
}
sub vcl_deliver {
if (req.url ~ "^/vd/normalizeUa" && resp.status == 200 &&
req.http.X-Orig-URL) {
set req.http.Fastly-force-Shield = "1";
if (req.http.X-Orig-URL ~ "?") {
set req.url = req.http.X-Orig-URL "&ua=" resp.http.UA;
} else {
set req.url = req.http.X-Orig-URL "?ua="
resp.http.UA;
}
restart;
} else if (req.url ~ "^/vd/polyfill..*[?&]ua=" &&
req.http.X-Orig-URL && req.http.X-Orig-URL !~ "[?&]ua=") {
add resp.http.Vary = "User-Agent";
}
return(deliver);
}
25. Authentication
Implement integration with your
federated identity system
entirely in VCL
This is great if...
You have a federated login system
using a protocol like OAuth
You want to annotate requests with a
simple verified authentication state
3
29. Authentication: key tools and techniques
● Get a cookie by name: req.http.Cookie:MySiteAuth
● Base64 normalisation:
digest.base64url_decode(), digest.base64_decode
● Extract the parts of a JSON Web Token (JWT):
regsub({{cookie}}, "(^[^.]+).[^.]+.[^.]+$", "1");
● Check JWT signature: digest.hmac_sha256_base64()
● Set trusted headers for backend use:
req.http.Nikkei-UserID = regsub({{jwt}}, {{pattern}}, "1");
29
30. authentication.vcl
30
if (req.http.Cookie:NikkeiAuth) {
set req.http.tmpHeader = regsub(req.http.Cookie:NikkeiAuth, "(^[^.]+).[^.]+.[^.]+$", "1");
set req.http.tmpPayload = regsub(req.http.Cookie:NikkeiAuth, "^[^.]+.([^.]+).[^.]+$", "1");
set req.http.tmpRequestSig = digest.base64url_decode(
regsub(req.http.Cookie:NikkeiAuth, "^[^.]+.[^.]+.([^.]+)$", "1")
);
set req.http.tmpCorrectSig = digest.base64_decode(
digest.hmac_sha256_base64("{{jwt_secret}}", req.http.tmpHeader "." req.http.tmpPayload)
);
if (req.http.tmpRequestSig != req.http.tmpCorrectSig) {
error 754 "/login; NikkeiAuth=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT";
}
... continues ...
31. authentication.vcl (cont)
31
set req.http.tmpPayload = digest.base64_decode(req.http.tmpPayload);
set req.http.Nikkei-UserID = regsub(req.http.tmpPayload, {"^.*?"sub"s*:s*"(w+)".*?$"}, "1");
set req.http.Nikkei-Rank = regsub(req.http.tmpPayload, {"^.*?"ds_rank"s*:s*"(w+)".*?$"}, "1");
unset req.http.base64_header;
unset req.http.base64_payload;
unset req.http.signature;
unset req.http.valid_signature;
unset req.http.payload;
} else {
set req.http.Nikkei-UserID = "anonymous";
set req.http.Nikkei-Rank = "anonymous";
}
32. Feature flags
Dark deployments and easy A/B
testing without reducing front
end perf or cache efficiency
This is great if...
You want to serve different versions
of your site to different users
Test new features internally on prod
before releasing them to the world
4
35. Feature flags parts
35
● A flags registry - a JSON file will be fine
○ Include all possible values of each flag and what percentage of the audience it applies to
○ Publish it statically - S3 is good for that
● A flag toggler tool
○ Reads the JSON, renders a table, writes an override cookie with chosen values
● An API
○ Reads the JSON, responds to requests by calculating a user's position number on a 0-100
line and matches them with appropriate flag values
36. Feature flags
36
Flags API
Article
Merge the flags response with the override cookie,
set as HTTP header, restart original request...
/article/123
Cookie: Flgs-
Override= Foo=10;
/api/flags?userid=6453
Flgs: highlights=true; Foo=42;
Flgs: highlights=true; Foo=42; Foo=10
Vary: Flgs
38. Dynamic backends
Override backend rules at
runtime without updating your
VCL
This is great if...
You have a bug you can't reproduce
without the request going through
the CDN
You want to test a local dev version of
a service with live integrations
5
40. Dynamic backends: key tools and techniques
● Extract backend to override:
set req.http.tmpORBackend =
regsub(req.http.Backend-Override, "s*->.*$", "");
● Check whether current backend matches
if (req.http.tmpORBackend == req.http.tmpCurrentBackend) {
● Use node-http-proxy for the proxy app
○ Remember res.setHeader('Vary', 'Backend-Override');
○ I use {xfwd: false, changeOrigin: true, hostRewrite: true}
40
41. Debug headers
Collect request lifecycle
information in a single HTTP
response header
This is great if...
You find it hard to understand what
path the request is taking through
your VCL
You have restarts in your VCL and
need to see all the individual
backend requests, not just the last
one
6
47. RUM++
Resource Timing API + data
Fastly exposes in VCL. And no
backend.
This is great if...
You want to track down hotspots of
slow response times
You'd like to understand how
successfully end users are being
matched to their nearest PoPs
7
48. Resource timing on front end
48
var rec = window.performance.getEntriesByType("resource")
.find(rec => rec.name.indexOf('[URL]') !== -1)
;
(new Image()).src = '/sendBeacon'+
'?dns='+(rec.domainLookupEnd-rec.domainLookupStart)+
'&connect='+(rec.connectEnd-rec.connectStart)+
'&req='+(rec.responseStart-rec.requestStart)+
'&resp='+(rec.responseEnd-rec.responseStart)
;
49. Add CDN data in VCL & respond with synthetic
49
sub vcl_recv {
if (req.url ~ "^/sendBeacon") {
error 204 "No content";
}
}
53. Beyond ASCII
Use these encoding tips to
embed non-ASCII content in
your VCL file.
This is great if...
Your users don't speak English, but
you can only write ASCII in VCL
files
8
61. Varnishlog to the rescue
A way to submit a varnish
transaction ID to the API,
and get all varnishlog
events relating to that
transaction, including
related (backend)
transactions
61
> fastly log 1467852934
17 SessionOpen c 66.249.72.22 47013 :80
17 ReqStart c 66.249.72.22 47013
1467852934
17 RxRequest c GET
17 RxURL c /articles/123
17 RxProtocol c HTTP/1.1
17 RxHeader c Host: www.example.com
...