Slide 1

Slide 1 text

By Thijs Feryn Caching with Varnish

Slide 2

Slide 2 text

Slow websites suck

Slide 3

Slide 3 text

Web performance is an essential part of the user experience

Slide 4

Slide 4 text

Slow ~ Down

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Mo money Mo servers

Slide 9

Slide 9 text

Identify slowest parts

Slide 10

Slide 10 text

Reduce the impact of the code on the server

Slide 11

Slide 11 text

Not just raw speed

Slide 12

Slide 12 text

Scale

Slide 13

Slide 13 text

Refactor slow code Write fast code

Slide 14

Slide 14 text

Optimize database

Slide 15

Slide 15 text

Improve the API call

Slide 16

Slide 16 text

Optimize runtime

Slide 17

Slide 17 text

After a while you hit the limits

Slide 18

Slide 18 text

Cache

Slide 19

Slide 19 text

Don’t recompute if the data hasn’t changed

Slide 20

Slide 20 text

Caching is not a compensation for poor code

Slide 21

Slide 21 text

Caching is an essential architectural strategy

Slide 22

Slide 22 text

Normally User Server

Slide 23

Slide 23 text

With Varnish User Varnish Server

Slide 24

Slide 24 text

Hi, I’m Thijs

Slide 25

Slide 25 text

I’m @ThijsFeryn on Twitter

Slide 26

Slide 26 text

I’m an Evangelist At

Slide 27

Slide 27 text

I’m an Evangelist At

Slide 28

Slide 28 text

I’m a at board member

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

Install & configure

Slide 31

Slide 31 text

https://packagecloud.io/varnishcache/varnish5 https://packagecloud.io/varnishcache/varnish41

Slide 32

Slide 32 text

$ curl -s https://packagecloud.io/install/repositories/ varnishcache/varnish5/script.deb.sh | sudo bash $ apt-get install varnish Debian & Ubuntu

Slide 33

Slide 33 text

$ apt-cache policy varnish varnish: Installed: 5.2.0-1~stretch Candidate: 5.2.0-1~stretch Version table: *** 5.2.0-1~stretch 500 500 https://packagecloud.io/varnishcache/varnish5/debian stretch/main amd64 Packages 100 /var/lib/dpkg/status 5.1.3-1~stretch 500 500 https://packagecloud.io/varnishcache/varnish5/debian stretch/main amd64 Packages 5.1.2-1~stretch 500 500 https://packagecloud.io/varnishcache/varnish5/debian stretch/main amd64 Packages 5.0.0-7+deb9u1 500 500 http://deb.debian.org/debian stretch/main amd64 Packages 500 http://security.debian.org stretch/updates/main amd64 Packages

Slide 34

Slide 34 text

$ apt-get install varnish=5.2.0-1~stretch

Slide 35

Slide 35 text

cp /lib/systemd/system/varnish.service \ /etc/systemd/system/ Copy defaults file Ubuntu & Debian with systemd

Slide 36

Slide 36 text

Startup options

Slide 37

Slide 37 text

[Unit] Description=Varnish Cache, a high-performance HTTP accelerator [Service] Type=forking # Maximum number of open files (for ulimit -n) LimitNOFILE=131072 # Locked shared memory - should suffice to lock the shared memory log # (varnishd -l argument) # Default log size is 80MB vsl + 1M vsm + header -> 82MB # unit is bytes LimitMEMLOCK=85983232 # On systemd >= 228 enable this to avoid "fork failed" on reload. #TasksMax=infinity # Maximum size of the corefile. LimitCORE=infinity # Set WARMUP_TIME to force a delay in reload-vcl between vcl.load and vcl.use # This is useful when backend probe definitions need some time before declaring # configured backends healthy, to avoid routing traffic to a non-healthy backend. #WARMUP_TIME=0 ExecStart=/usr/sbin/varnishd -a :80 -T localhost:6082 -f /etc/varnish/default.vcl -S /etc/varnish/ secret -s malloc,256m ExecReload=/usr/share/varnish/reload-vcl [Install] WantedBy=multi-user.target Debian Stretch with systemd

Slide 38

Slide 38 text

$ vim /etc/systemd/system/varnish.service $ systemctl daemon-reload $ service varnish reload Reload with systemd

Slide 39

Slide 39 text

DAEMON_OPTS="-j unix,user=www-data \ -a main=0000:80 \ -a proxy=0.0.0.0:81,PROXY \ -T localhost:6082 \ -f /etc/varnish/default.vcl \ -S /etc/varnish/secret \ -l 100m,10m \ -t 60 \ -p feature=+esi_disable_xml_check \ -p connect_timeout=20s \ -p first_byte_timeout=100s \ -p between_bytes_timeout=5s \ -s malloc,3g"

Slide 40

Slide 40 text

The backend

Slide 41

Slide 41 text

Point of entry? Bind to 80 Otherwise 6081

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

DAEMON_OPTS="-j unix,user=www-data \ -a main=0.0.0.0:80 \ -a proxy=0.0.0.0:81,PROXY \ -T localhost:6082 \ -b 127.0.0.1:8080 \ -S /etc/varnish/secret \ -s malloc,3g" Can't have -f In config file

Slide 44

Slide 44 text

vcl 4.0;
 
 backend default {
 .host = "127.0.0.1";
 .port = "8080";
 } In VCL file Minimal VCL

Slide 45

Slide 45 text

What about TLS/SSL?

Slide 46

Slide 46 text

✓HAProxy ✓Nginx ✓Pound ✓Hitch Terminate TLS/SSL

Slide 47

Slide 47 text

HAProxy 172.18.0.3 Varnish 172.18.0.8 Nginx Public HTTP & HTTPS HTTP HTTP Certificates go here

Slide 48

Slide 48 text

How do you know if it was HTTP or HTTPS?

Slide 49

Slide 49 text

X-Forwarded-Proto: https X-Forwarded-Proto: http

Slide 50

Slide 50 text

if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) $_SERVER['HTTPS']='on';

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

SetEnvIf X-Forwarded-Proto "https" HTTPS=on Header append Vary: X-Forwarded-Proto RewriteEngine on RewriteCond %{HTTPS} !=on RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC] RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Slide 53

Slide 53 text

What’s the client’s IP?

Slide 54

Slide 54 text

HAProxy 172.18.0.3 Varnish 172.18.0.8 Nginx Public 172.18.0.1

Slide 55

Slide 55 text

X-Forwarded-For

Slide 56

Slide 56 text

Nginx Public 172.18.0.1 Client IP: 172.18.0.1 X-Forwarded-For:

Slide 57

Slide 57 text

Varnish 172.18.0.8 Nginx Public 172.18.0.1 Client IP: 172.18.0.8 X-Forwarded-For: 172.18.0.1

Slide 58

Slide 58 text

HAProxy 172.18.0.3 Varnish 172.18.0.8 Nginx Public 172.18.0.1 Client IP: 172.18.0.8 X-Forwarded-For: 172.18.0.3

Slide 59

Slide 59 text

PROXY Protocol

Slide 60

Slide 60 text

Add original IP as TCP preamble

Slide 61

Slide 61 text

DAEMON_OPTS="-j unix,user=www-data \ -a :80 \ -a :81,PROXY \ -T localhost:6082 \ -f /etc/varnish/default.vcl \ -S /etc/varnish/secret \ -l 100m,10m \ -t 60 \ -p feature=+esi_disable_xml_check \ -p connect_timeout=20s \ -p first_byte_timeout=100s \ -p between_bytes_timeout=5s \ -s malloc,3g"

Slide 62

Slide 62 text

Varnish automatically sets X-Forwarded-For to the original IP using PROXY protocol

Slide 63

Slide 63 text

HAProxy 172.18.0.3 Varnish 172.18.0.8 Nginx Public 172.18.0.1 Client IP: 172.18.0.8 X-Forwarded-For: 172.18.0.1

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

There are rules

Slide 66

Slide 66 text

✓Idempotence ✓State ✓Expiration ✓Conditional requests ✓Cache variations Varnish speaks HTTP

Slide 67

Slide 67 text

Idempotence Execute multiple times Result doesn't change

Slide 68

Slide 68 text

✓ GET ✓ HEAD - POST - PUT - DELETE - PATCH Idempotence Result changes Result doesn't change Don't cache

Slide 69

Slide 69 text

State

Slide 70

Slide 70 text

State ~ user specific data Cookies Auth headers

Slide 71

Slide 71 text

About cookies

Slide 72

Slide 72 text

✓ Request header ✓ Sent by client (~browser) ✓ Sent on every request ✓ Describes state ✓ Is not cached ✓ 3rd party tracking cookies vs own cookies About cookies Cookie: key=value;key2=value2

Slide 73

Slide 73 text

✓ Response header ✓ Sent by backend server ✓ Only when backend is called ✓ Changes state ✓ Is not cached ✓ Blacklisted for 120s (by default) About cookies Set-Cookie: key=another_value

Slide 74

Slide 74 text

Time To Live

Slide 75

Slide 75 text

✓ 120s by default ✓ Respects HTTP cache-control header ✓ Respects expires header ✓ Override in VCL file Time to live

Slide 76

Slide 76 text

1.VCL 2.s-maxage 3.max-age 4.expires 5.Default value 120s TTL Order

Slide 77

Slide 77 text

Use TTL not to cache Cache-Control: max-age=0 Cache-Control: s-maxage=0 Cache-Control: private Cache-Control: no-cache Cache-Control: no-store Expires: Fri, 1 Jan 1971 00:00:00 GMT

Slide 78

Slide 78 text

Combine values Cache-Control: public, max- age=1000, s-maxage=3600

Slide 79

Slide 79 text

Age Age: 10 How old is the cached object?

Slide 80

Slide 80 text

Real cache duration max-age - Age s-maxage - Age

Slide 81

Slide 81 text

Don’t set the Age header yourself!

Slide 82

Slide 82 text

Conditional requests

Slide 83

Slide 83 text

Only fetch payload that has changed

Slide 84

Slide 84 text

Otherwise: HTTP/1.1 304 Not Modified

Slide 85

Slide 85 text

In both directions User Varnish Server 304 Not Modified 304 Not Modified

Slide 86

Slide 86 text

✓ Conserve bandwidth ✓ Reduce load on the backend when revalidating Conditional requests

Slide 87

Slide 87 text

Conditional requests HTTP/1.1 200 OK Host: localhost Etag: 7c9d70604c6061da9bb9377d3f00eb27 Content-type: text/html; charset=UTF-8 Hello world output GET /if_none_match.php HTTP/1.1 Host: localhost User-Agent: curl/7.48.0

Slide 88

Slide 88 text

Conditional requests HTTP/1.0 304 Not Modified Host: localhost Etag: 7c9d70604c6061da9bb9377d3f00eb27 GET /if_none_match.php HTTP/1.1 Host: localhost User-Agent: curl/7.48.0 If-None-Match: 7c9d70604c6061da9bb9377d3f00eb27

Slide 89

Slide 89 text

Conditional requests HTTP/1.1 200 OK Host: localhost Last-Modified: Fri, 22 Jul 2016 10:11:16 GMT Content-type: text/html; charset=UTF-8 Hello world output GET /if_none_match.php HTTP/1.1 Host: localhost User-Agent: curl/7.48.0

Slide 90

Slide 90 text

Conditional requests HTTP/1.0 304 Not Modified Host: localhost Last-Modified: Fri, 22 Jul 2016 10:11:16 GMT GET /if_none_match.php HTTP/1.1 Host: localhost User-Agent: curl/7.48.0 If-Last-Modified: Fri, 22 Jul 2016 10:11:16 GMT

Slide 91

Slide 91 text

Store etag or modification date

Slide 92

Slide 92 text

Validate early on

Slide 93

Slide 93 text

Exit quickly

Slide 94

Slide 94 text

Varnish can revalidate aynchronously

Slide 95

Slide 95 text

And serve stale data while that happens

Slide 96

Slide 96 text

Grace mode

Slide 97

Slide 97 text

Effective TTL = TTL + grace

Slide 98

Slide 98 text

Cache-Control: public, max-age=100, s-maxage=3600, stale-while-revalidate=7200 Stale-While-Revalidate Set grace mode

Slide 99

Slide 99 text

Variations

Slide 100

Slide 100 text

Cache is not the same for everyone

Slide 101

Slide 101 text

✓ URL ✓ Hostname ✓ IP if hostname is not set ✓ Vary header Basic variations

Slide 102

Slide 102 text

Vary header Vary: Accept-Encoding Vary: Accept-Language Vary: Cookie Watch out!

Slide 103

Slide 103 text

The flow

Slide 104

Slide 104 text

No content

Slide 105

Slide 105 text

No content

Slide 106

Slide 106 text

Varnish Configuration Language

Slide 107

Slide 107 text

DSL compiled and linked as shared object

Slide 108

Slide 108 text

/etc/varnish/default.vcl

Slide 109

Slide 109 text

Hooks & subroutines

Slide 110

Slide 110 text

✓vcl_recv: receive request ✓vcl_hash: compose cache key ✓vcl_miss: not found in cache ✓vcl_hit: found in cache ✓vcl_pass: don’t store in cache ✓vcl_pipe: bypass cache ✓vcl_backend_fetch: connect to backend ✓vcl_backend_response: response from backend ✓vcl_backend_error: backend fetch failed

Slide 111

Slide 111 text

✓vcl_purge: after successful purge ✓vcl_synth: send synthetic output ✓vcl_deliver: return data to client ✓vcl_init: initialize VMODs ✓vcl_fini: discard VMODs ✓vcl_fail: stop execution

Slide 112

Slide 112 text

Actions

Slide 113

Slide 113 text

✓ hash: lookup in cache ✓ pass: don't cache ✓ synth: synthetic HTML output ✓ pipe: bypass cache ✓ purge: remove from cache VCL_RECV

Slide 114

Slide 114 text

✓ synth: synthetic HTML output ✓ pipe: bypass cache VCL_PIPE

Slide 115

Slide 115 text

✓ fetch: fetch data from backend, don't cache ✓ restart: restart transaction ✓ synth: synthetic HTML output VCL_PASS

Slide 116

Slide 116 text

✓ deliver: send cached object ✓ miss: synchronous refresh despite hit ✓ pass: fetch data from backend despite hit, don't cache ✓ restart: restart transaction ✓ synth: synthetic HTML output VCL_HIT

Slide 117

Slide 117 text

✓ fetch: fetch data from backend ✓ pass: fetch data from backend, don't cache ✓ restart: restart transaction ✓ synth: synthetic HTML output VCL_MISS

Slide 118

Slide 118 text

✓ lookup: look for cached object by using hash key VCL_HASH

Slide 119

Slide 119 text

✓ restart: restart transaction ✓ synth: synthetic HTML output VCL_PURGE

Slide 120

Slide 120 text

✓ deliver: deliver object to client ✓ restart: restart transaction ✓ synth: synthetic HTML output VCL_DELIVER

Slide 121

Slide 121 text

✓ deliver: deliver synthetic output to client ✓ restart: restart transaction VCL_SYNTH

Slide 122

Slide 122 text

✓ fetch: fetch object from backend ✓ abandon: abandon request and send HTTP 503 error VCL_BACKEND_FETCH

Slide 123

Slide 123 text

✓ deliver: send fetched data to client ✓ abandon: abandon request and send HTTP 503 error ✓ retry: retry backend request VCL_BACKEND_RESPONSE

Slide 124

Slide 124 text

✓ deliver: send fetched data to client ✓ retry: retry backend request VCL_BACKEND_ERROR

Slide 125

Slide 125 text

Typical flows

Slide 126

Slide 126 text

✓ vcl_recv: hash ✓ vcl_hash: lookup ✓ vcl_miss: fetch ✓ vcl_backend_request: fetch ✓ vcl_backend_response: deliver ✓ vcl_deliver: deliver MISS

Slide 127

Slide 127 text

✓ vcl_recv: hash ✓ vcl_hash: lookup ✓ vcl_hit: deliver ✓ vcl_deliver: deliver HIT

Slide 128

Slide 128 text

✓ vcl_recv: pass ✓ vcl_pass: fetch ✓ vcl_backend_request: fetch ✓ vcl_backend_response: deliver ✓ vcl_deliver: deliver PASS

Slide 129

Slide 129 text

Objects

Slide 130

Slide 130 text

✓ req: incoming request object ✓ req_top: top level esi request ✓ bereq: request object to send to backend ✓ beresp: backend response ✓ resp: response to send back to client ✓ obj: cached object ✓ client: client information ✓ server: server information ✓ local: local TCP information ✓ remote: remote TCP information ✓ storage: storage information VCL Objects

Slide 131

Slide 131 text

Variables

Slide 132

Slide 132 text

✓ req.url ✓ req.http.host ✓ req.http.user-agent ✓ req.backend_hint ✓ req.method ✓ … req variables

Slide 133

Slide 133 text

✓ bereq.backend ✓ bereq.http.user-agent ✓ bereq.method ✓ bereq.url ✓ … bereq variables

Slide 134

Slide 134 text

✓ beresp.age ✓ beresp.backend.ip ✓ beresp.backend.name ✓ beresp.do_esi ✓ beresp.grace ✓ beresp.keep ✓ beresp.http.set-cookie ✓ beresp.ttl ✓ beresp.status ✓ beresp.uncacheable beresp variables

Slide 135

Slide 135 text

✓ client.ip ✓ client.identity client variables local variables ✓ local.ip remote variables ✓ remote.ip

Slide 136

Slide 136 text

✓ obj.age ✓ obj.grace ✓ obj.hits ✓ obj.http.cache-control ✓ obj.reason ✓ obj.status ✓ obj.ttl object variables

Slide 137

Slide 137 text

✓ resp.http.user-agent ✓ resp.is_streaming ✓ resp.reason ✓ resp.status resp variables

Slide 138

Slide 138 text

✓ storage..free_space ✓ storage..used_space ✓ storage..happy storage variables

Slide 139

Slide 139 text

Default behaviour

Slide 140

Slide 140 text

vcl 4.0; sub vcl_recv { if (req.method == "PRI") { return (synth(405)); } if (req.method != "GET" && req.method != "HEAD" && req.method != "PUT" && req.method != "POST" && req.method != "TRACE" && req.method != "OPTIONS" && req.method != “PATCH" && req.method != "DELETE") { return (pipe); } if (req.method != "GET" && req.method != "HEAD") { return (pass); } if (req.http.Authorization || req.http.Cookie) { return (pass); } return (hash); } Idempotence State Action Receive request

Slide 141

Slide 141 text

sub vcl_hash { hash_data(req.url); if (req.http.host) { hash_data(req.http.host); } else { hash_data(server.ip); } return (lookup); } Lookup in cache Variations Action

Slide 142

Slide 142 text

sub vcl_purge { return (synth(200, "Purged")); } sub vcl_hit { if (obj.ttl >= 0s) { return (deliver); } if (obj.ttl + obj.grace > 0s) { return (deliver); } return (miss); } sub vcl_miss { return (fetch); } sub vcl_deliver { return (deliver); } Remove from cache Found in cache Not found in cache Return HTTP response to client

Slide 143

Slide 143 text

sub vcl_synth { set resp.http.Content-Type = "text/html; charset=utf-8"; set resp.http.Retry-After = "5"; synthetic( {" "} + resp.status + " " + resp.reason + {"

Error "} + resp.status + " " + resp.reason + {"

"} + resp.reason + {"

Guru Meditation:

XID: "} + req.xid + {"


Varnish cache server

"} ); return (deliver); } Send custom HTML

Slide 144

Slide 144 text

sub vcl_backend_fetch { return (fetch); } sub vcl_backend_response { if (bereq.uncacheable) { return (deliver); } else if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Surrogate-control ~ "no-store" || (!beresp.http.Surrogate-Control && beresp.http.Cache-Control ~ "no-cache|no-store|private") || beresp.http.Vary == "*") { set beresp.ttl = 120s; set beresp.uncacheable = true; } return (deliver); } Send backend request Receive backend response TTL State Receive backend response

Slide 145

Slide 145 text

sub vcl_backend_error { set beresp.http.Content-Type = "text/html; charset=utf-8"; set beresp.http.Retry-After = "5"; synthetic( {" "} + beresp.status + " " + beresp.reason + {"

Error "} + beresp.status + " " + beresp.reason + {"

"} + beresp.reason + {"

Guru Meditation:

XID: "} + bereq.xid + {"


Varnish cache server

"} ); return (deliver); } Send custom HTML on backend error

Slide 146

Slide 146 text

VMOD

Slide 147

Slide 147 text

sub vcl_recv { if(std.file_exists("/etc/varnish/file.txt")) { return(synth(200,std.fileread("/etc/varnish/file.txt"))); } else { return(synth(200,"File does not exist")); } } std.fileread

Slide 148

Slide 148 text

sub vcl_recv { return(synth(200,"FOO: " + std.getenv("FOO"))); } std.getenv

Slide 149

Slide 149 text

vcl 4.0; import std; backend default { .host = "nginx"; .port = "80"; .probe = { .url = "/"; .interval = 1s; .timeout = 1s; .window = 5; .threshold = 3; } } sub vcl_recv { if(std.healthy(req.backend_hint)) { return(synth(200,"Backend is healthy")); } else { return(synth(200,"Backend is unhealthy")); } } std.healthy

Slide 150

Slide 150 text

sub vcl_recv { std.timestamp("Before std.log"); std.log("This should appear in the Shared Memory Log"); std.timestamp("After std.log"); std.syslog(9,"This should appear in the Syslog"); std.timestamp("After std.syslog"); return(synth(200,"OK")); } Logging

Slide 151

Slide 151 text

sub vcl_recv { return(synth(200,"Client port: " + std.port(client.ip) + ", server port: " + std.port(server.ip))); } std.port

Slide 152

Slide 152 text

sub vcl_recv { return(synth(200,"Unsorted: " + regsub(req.url,"\?(.+)$","\1") + ", sorted: " + regsub(std.querysort(req.url),"\?(.+)$","\1"))); } std.querysort

Slide 153

Slide 153 text

https://varnish-cache.org/docs/5.2/ reference/vmod_std.generated.html

Slide 154

Slide 154 text

Install other VMODs

Slide 155

Slide 155 text

apt-get install -y varnish varnish-dev build-essential curl -O https://download.varnish-software.com/varnish- modules/varnish-modules-0.12.1.tar.gz tar xvzf varnish-modules-0.12.1.tar.gz cd varnish-modules-0.12.1 ./configure make make install Install official VMODs

Slide 156

Slide 156 text

vmod_cookie

Slide 157

Slide 157 text

vcl 4.0; sub vcl_recv { if (req.http.Cookie) { set req.http.Cookie = ";" + req.http.Cookie; set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1="); set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); if (req.http.cookie ~ "^\s*$") { unset req.http.cookie; } } } Without vmod_cookie

Slide 158

Slide 158 text

vcl 4.0; import cookie; sub vcl_recv { cookie.parse(req.http.cookie); if(cookie.isset(“PHPSESSID") && req.url ~ "^/products/[0-9]+$") { cookie.delete("PHPSESSID"); } set req.http.cookie = cookie.get_string(); } With vmod_cookie

Slide 159

Slide 159 text

https://github.com/varnish/varnish- modules/blob/master/docs/ vmod_cookie.rst

Slide 160

Slide 160 text

vmod_accept

Slide 161

Slide 161 text

https://github.com/gquintard/libvmod-accept

Slide 162

Slide 162 text

vcl 4.0; import accept; backend default { .host = "nginx"; .port = "80"; } sub vcl_init { new rule = accept.rule("en"); rule.add("nl"); } sub vcl_recv { set req.http.x-old-accept-language= req.http.Accept-Language; set req.http.Accept-Language = rule.filter(req.http.Accept-Language); return(synth(200,"
  • Raw Accept-Language: "+req.http.x-old-accept- language+"
  • Filtered Accept-Language: "+req.http.Accept-Language+"
  • ul>")); } vmod_accept

Slide 163

Slide 163 text

• Raw Accept-Language: nl,en-US;q=0.8,en;q=0.6 • Filtered Accept-Language: nl vmod_accept output

Slide 164

Slide 164 text

vcl 4.0; import accept; backend default { .host = "nginx"; .port = "80"; } sub vcl_init { new language = accept.rule("en"); language.add("nl"); new encoding = accept.rule("deflate"); language.add("gzip"); new contenttype = accept.rule("text/plain"); contenttype.add("text/html"); contenttype.add("application/json"); } sub vcl_recv { set req.http.Accept-Language = language.filter(req.http.Accept-Language); set req.http.Accept-Encoding = encoding.filter(req.http.Accept-Encoding); set req.http.Accept = contenttype.filter(req.http.Accept); } vmod_accept

Slide 165

Slide 165 text

Why is this useful?

Slide 166

Slide 166 text

Accept-Language: nl,en-US;q=0.8,en;q=0.6 Too many variations Impacts hit rate Why is vmod_accept useful? Accept-Language: nl Less variations

Slide 167

Slide 167 text

In an ideal world

Slide 168

Slide 168 text

HTTP best practices > custom VCL

Slide 169

Slide 169 text

Reality sucks

Slide 170

Slide 170 text

No content

Slide 171

Slide 171 text

No content

Slide 172

Slide 172 text

Don’t trust the end-user

Slide 173

Slide 173 text

Cache-control ?

Slide 174

Slide 174 text

Legacy

Slide 175

Slide 175 text

Write VCL

Slide 176

Slide 176 text

Normalize

Slide 177

Slide 177 text

vcl 4.0; import accept; import std; backend default { .host = "nginx"; .port = "80"; } sub vcl_init { new language = accept.rule("en"); language.add("nl"); new encoding = accept.rule(""); language.add("deflate"); language.add("gzip"); new contenttype = accept.rule("text/plain"); contenttype.add("text/html"); contenttype.add("application/json"); } sub vcl_recv { set req.http.Accept-Language = language.filter(req.http.Accept-Language); set req.http.Accept-Encoding = encoding.filter(req.http.Accept-Encoding); set req.http.Accept = contenttype.filter(req.http.Accept); if (req.http.User-Agent ~ "MSIE 6" || req.http.Accept-Encoding ~ "^\s*$") { unset req.http.Accept-Encoding; } set req.http.Host = regsub(req.http.Host, ":[0-9]+", ""); set req.url = std.querysort(req.url); Normalize

Slide 178

Slide 178 text

if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") { set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof| siteurl)=([A-z0-9_\-\.%25]+)", ""); set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof| siteurl)=([A-z0-9_\-\.%25]+)", "?"); set req.url = regsub(req.url, "\?&", "?"); set req.url = regsub(req.url, "\?$", ""); } if (req.url ~ "\#") { set req.url = regsub(req.url, "\#.*$", ""); } if (req.url ~ "\?$") { set req.url = regsub(req.url, "\?$", ""); } } Normalize

Slide 179

Slide 179 text

Static assets

Slide 180

Slide 180 text

vcl 4.0; sub vcl_recv { if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv| gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|ogg| ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz| wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") { unset req.http.Cookie; return (hash); } } sub vcl_backend_response { if (bereq.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac| flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf| ogg|ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt| txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") { unset beresp.http.set-cookie; } if (bereq.url ~ "^[^?]*\.(7z|avi|bz2|flac|flv|gz|mka|mkv|mov|mp3|mp4| mpeg|mpg|ogg|ogm|opus|rar|tar|tgz|tbz|txz|wav|webm|xz|zip)(\?.*)?$") { unset beresp.http.set-cookie; set beresp.do_stream = true; set beresp.do_gzip = false; } } Cache static assets

Slide 181

Slide 181 text

Do you really want to cache static assets?

Slide 182

Slide 182 text

Nginx or Apache can be fast enough for that

Slide 183

Slide 183 text

vcl 4.0; import std; sub vcl_recv { if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv| gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|ogg| ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz| wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") { unset req.http.Cookie; return (pass); } } Don’t cache static assets

Slide 184

Slide 184 text

URL whitelist/blacklist

Slide 185

Slide 185 text

sub vcl_recv { if (req.url ~ "^/status\.php$" || req.url ~ "^/update\.php$" || req.url ~ "^/admin$" || req.url ~ "^/admin/.*$" || req.url ~ "^/user$" || req.url ~ "^/user/.*$" || req.url ~ "^/flag/.*$" || req.url ~ "^.*/ajax/.*$" || req.url ~ "^.*/ahah/.*$") { return (pass); } } URL blacklist

Slide 186

Slide 186 text

sub vcl_recv { if (req.url ~ "^/products/?" return (hash); } } URL whitelist

Slide 187

Slide 187 text

Those damn cookies again!

Slide 188

Slide 188 text

vcl 4.0; sub vcl_recv { set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "_ga=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "_gat=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "utmctr=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "utmcmd.=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "utmccn.=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "__gads=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "__atuv.=[^;]+(; )?", ""); set req.http.Cookie = regsuball(req.http.Cookie, "^;\s*", ""); if (req.http.cookie ~ "^\s*$") { unset req.http.cookie; } } Remove tracking cookies

Slide 189

Slide 189 text

vcl 4.0; sub vcl_recv { if (req.http.Cookie) { set req.http.Cookie = ";" + req.http.Cookie; set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1="); set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); if (req.http.cookie ~ "^\s*$") { unset req.http.cookie; } } } Only keep session cookie

Slide 190

Slide 190 text

vcl 4.0; sub vcl_recv { if (req.http.cookie) { cookie.parse(req.http.cookie); cookie.filter_except("PHPSESSID"); set req.http.cookie = cookie.get_string(); if (req.http.cookie ~ "^\s*$") { unset req.http.cookie; } } } The VMOD way

Slide 191

Slide 191 text

sub vcl_recv { if (req.http.Cookie) { set req.http.Cookie = ";" + req.http.Cookie; set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); set req.http.Cookie = regsuball(req.http.Cookie, ";(language)=", "; \1="); set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); if (req.http.cookie ~ "^\s*$") { unset req.http.cookie; return(pass); } return(hash); } } sub vcl_hash { hash_data(regsub( req.http.Cookie, "^.*language=([^;]*);*.*$", "\1" )); } Language cookie cache variation

Slide 192

Slide 192 text

vcl 4.0; import cookie; backend default { .host = "nginx"; .port = "80"; } sub vcl_recv { if (req.http.cookie) { cookie.parse(req.http.cookie); cookie.filter_except("language"); set req.http.cookie = cookie.get_string(); if (req.http.cookie ~ "^\s*$") { unset req.http.cookie; } return(hash); } } sub vcl_hash { hash_data(cookie.get("language")); } The VMOD way

Slide 193

Slide 193 text

sub vcl_recv { return(hash); } Cache, even with cookies

Slide 194

Slide 194 text

sub vcl_hash { hash_data(req.http.Cookie); } Hash all cookies

Slide 195

Slide 195 text

Block caching

Slide 196

Slide 196 text

No content

Slide 197

Slide 197 text

Code renders single HTTP response

Slide 198

Slide 198 text

Lowest denominator: no cache

Slide 199

Slide 199 text

Edge Side Includes ✓Placeholder ✓Parsed by Varnish ✓Output is a composition of blocks ✓State per block ✓TTL per block

Slide 200

Slide 200 text

sub vcl_recv { set req.http.Surrogate-Capability = "key=ESI/1.0"; } sub vcl_backend_response { if (beresp.http.Surrogate-Control ~ "ESI/1.0") { unset beresp.http.Surrogate-Control; set beresp.do_esi = true; } } Edge Side Includes

Slide 201

Slide 201 text

'; '.PHP_EOL; echo "Date in the main page: ".date('Y-m-d H:i:s').'
'; Main page ESI frame: esi.php Cached for 10 seconds Not cached

Slide 202

Slide 202 text

req_top Get information about parent request in an ESI call

Slide 203

Slide 203 text

vcl 4.0; backend default { .host = "nginx"; .port = "80"; } sub vcl_recv { if (req.esi_level > 0) { set req.http.x-parent-url = req_top.url; } } sub vcl_backend_response { if (beresp.http.Surrogate-Control ~ "ESI/1.0") { unset beresp.http.Surrogate-Control; set beresp.do_esi = true; } } Get parent URL

Slide 204

Slide 204 text

ESI vs AJAX

Slide 205

Slide 205 text

✓ Server-side ✓ Standardized ✓ Processed on the “edge”, no in the browser ✓ Generally faster Edge-Side Includes - Sequential - One fails, all fail - Limited implementation in Varnish

Slide 206

Slide 206 text

✓ Client-side ✓ Common knowledge ✓ Parallel processing ✓ Graceful degradation AJAX - Processed by the browser - Extra roundtrips - Somewhat slower

Slide 207

Slide 207 text

Choose wisely

Slide 208

Slide 208 text

Assemble at the view layer

Slide 209

Slide 209 text

{{ render_esi(controller('AppBundle:News:latest', { 'maxPerPage': 5 })) }} {{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }} In your Twig templates Silex or Symfony Falls back to internal subrequests on failure Does ESI

Slide 210

Slide 210 text

{{ render_hinclude(controller('AppBundle:News:latest', { 'maxPerPage': 5 })) }} {{ render_hinclude(url(‘latest_news', { 'maxPerPage': 5 })) }} In your Twig templates Silex or Symfony Requires hinclude.js Does AJAX

Slide 211

Slide 211 text

ESI vs HInclude Extra parameters

Slide 212

Slide 212 text

Silex example

Slide 213

Slide 213 text

register(new Silex\Provider\TwigServiceProvider(), [ 'twig.path' => dirname(__DIR__).'/views' ]); $app->register(new Silex\Provider\HttpCacheServiceProvider()); $app->register(new Silex\Provider\HttpFragmentServiceProvider()); $app->get('/', function() use($app) { $response = new Response($app['twig']->render('index.twig')); $response->setSharedMaxAge(10); return $response; }); $app->get('/header', function() use($app) { $response = new Response($app['twig']->render('header.twig')); $response->setPrivate(); return $response; })->bind('header'); $app->get('/footer', function() use($app) { $response = new Response($app['twig']->render('footer.twig')); $response->setSharedMaxAge(3); return $response; })->bind('footer'); $app->run();

Slide 214

Slide 214 text

{{ render_esi(url('header')) }}

Main content

Date and time
{{ "now"|date("m/d/Y H:i:s") }}
{{ render_hinclude(url('footer')) }} script> </body> </html> index.twig

Slide 215

Slide 215 text

Header

Date and time
{{ "now"|date("m/d/Y H:i:s") }}

header.twig

Slide 216

Slide 216 text


Footer

Date and time
{{ "now"|date("m/d/Y H:i:s") }}
footer.twig

Slide 217

Slide 217 text

Control Time To Live

Slide 218

Slide 218 text

sub vcl_backend_response { set beresp.ttl = 3h; } Control Time To Live

Slide 219

Slide 219 text

sub vcl_backend_response { if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") { set beresp.ttl = 120s; set beresp.uncacheable = true; return (deliver); } } Control Time To Live

Slide 220

Slide 220 text

Debugging

Slide 221

Slide 221 text

sub vcl_deliver { if (obj.hits > 0) { set resp.http.X-Cache = "HIT"; } else { set resp.http.X-Cache = "MISS"; } } Debugging

Slide 222

Slide 222 text

Anonymize

Slide 223

Slide 223 text

sub vcl_deliver { unset resp.http.X-Powered-By; unset resp.http.Server; unset resp.http.X-Drupal-Cache; unset resp.http.X-Varnish; unset resp.http.Via; unset resp.http.Link; unset resp.http.X-Generator; } Anonymize

Slide 224

Slide 224 text

Breaking news isn't breaking

Slide 225

Slide 225 text

Purging

Slide 226

Slide 226 text

acl purgers { "localhost"; "127.0.0.1"; "::1"; } sub vcl_recv { if (req.method == "PURGE") { if (client.ip !~ purgers) { return (synth(405, "Method not allowed")); } return (purge); } } Purging

Slide 227

Slide 227 text

Purging curl -XPURGE "http://example.com/products" Immediately remove from memory

Slide 228

Slide 228 text

true, CURLOPT_CUSTOMREQUEST => 'PURGE' ]); echo curl_exec($curl).PHP_EOL; Purging

Slide 229

Slide 229 text

'http://localhost:6081/']); $response = $client->request('PURGE', '/'); if($response->getStatusCode() == 200) { echo "PURGED".PHP_EOL; } else { echo $response->getBody(); } Purging

Slide 230

Slide 230 text

acl purge { "localhost"; "127.0.0.1"; "::1"; } sub vcl_recv { if (req.method == "PURGE") { if (client.ip !~ purgers) { return (synth(405, "Method not allowed")); } if(req.http.x-purge-regex) { ban("req.http.host == " + req.http.host + " && req.url ~ " + req.http.x-purge-regex); } else { ban("req.http.host == " + req.http.host + " && req.url == " + req.url); } return (synth(200, "Purged")); } } Banning

Slide 231

Slide 231 text

Banning curl -XPURGE "http://example.com/products" curl -XPURGE -H "x-purge-regex:/products" "http://example.com" Add to ban list, remove on next request Remove patterns via “X-PURGE-REGEX”

Slide 232

Slide 232 text

Lurker-friendly bans

Slide 233

Slide 233 text

Ban lurker Object User Varnish Server Sends HTTP response Response stored in object Sends BAN to Varnish Ban lurker thread Ban list Reads ban list Removes object ban req.http.host == localhost && req.url ~ /products

Slide 234

Slide 234 text

Ban lurker ban req.http.host == localhost && req.url ~ /products Lurker can’t match request info Lurker can only match what’s in the object Next visitor triggers cache remove

Slide 235

Slide 235 text

acl purge { "localhost"; "127.0.0.1"; "::1"; } sub vcl_recv { if (req.method == "PURGE") { if (client.ip !~ purgers) { return (synth(405, "Method not allowed")); } if(req.http.x-purge-regex) { ban("obj.http.x-host == " + req.http.host + " && obj.http.x-url ~ " + req.http.x-purge-regex); } else { ban("obj.http.host == " + req.http.host + " && obj.http.x-url == " + req.url); } return (synth(200, "Purged")); } } sub vcl_backend_response { set beresp.http.x-url = bereq.url; set beresp.http.x-host = bereq.http.host; } sub vcl_deliver { unset resp.http.x-url; unset resp.http.x-host; } Lurker-friendly bans Store request info in response object

Slide 236

Slide 236 text

Lurker-friendly bans ban obj.http.x-host == localhost && obj.http.x-url ~ /products Lurker can match response info Ban lurker removes item from cache async

Slide 237

Slide 237 text

varnish> param.show ban_lurker_age 200 ban_lurker_age Value is: 60.000 [seconds] (default) Minimum is: 0.000 The ban lurker will ignore bans until they are this old. When a ban is added, the active traffic will be tested against it as part of object lookup. Because many applications issue bans in bursts, this parameter holds the ban-lurker off until the rush is over. This should be set to the approximate time which a ban-burst takes.

Slide 238

Slide 238 text

Banning through Varnishadm

Slide 239

Slide 239 text

varnishadm> ban obj.http.x-host == localhost && obj.http.x-url ~ /products Varnishadm banning

Slide 240

Slide 240 text

Banning through socket

Slide 241

Slide 241 text

Varnishadm is a binary on top of the socket

Slide 242

Slide 242 text

Varnishadm uses the secret file to authenticate automatically

Slide 243

Slide 243 text

$ telnet varnish 6082 Connected to localhost. Escape character is '^]'. 107 59 cohvooigdtqvkpwewhdxkqiwkfkpwsly Authentication required. auth 5a9c5722f31cc3c92f0e4616571624df7bddde2f8e42aaffe795dc80fb8c91dd 200 240 ----------------------------- Varnish Cache CLI 1.0 ----------------------------- Linux,4.9.49-moby,x86_64,-junix,-smalloc,-smalloc,-hcritbit varnish-5.2.0 revision 4c4875cbf Type 'help' for command list. Type 'quit' to close CLI session. ban obj.http.x-host == localhost && obj.http.x-url ~ /products 200 0

Slide 244

Slide 244 text

Slide 245

Slide 245 text

$ telnet varnish 6082 Connected to localhost. Escape character is '^]'. 107 59 cohvooigdtqvkpwewhdxkqiwkfkpwsly Authentication required. auth 5a9c5722f31cc3c92f0e4616571624df7bddde2f8e42aaffe795dc80fb8c91dd 200 240 ----------------------------- Varnish Cache CLI 1.0 ----------------------------- Linux,4.9.49-moby,x86_64,-junix,-smalloc,-smalloc,-hcritbit varnish-5.2.0 revision 4c4875cbf Type 'help' for command list. Type 'quit' to close CLI session. ban obj.http.x-host == localhost && obj.http.x-url ~ /products 200 0

Slide 246

Slide 246 text

Secure your access the the admin socket

Slide 247

Slide 247 text

DAEMON_OPTS="-j unix,user=www-data \ -a :80 \ -a :81,PROXY \ -T localhost:6082 \ -f /etc/varnish/default.vcl \ -S /etc/varnish/secret \ -l 100m,10m \ -t 60 \ -p feature=+esi_disable_xml_check \ -p connect_timeout=20s \ -p first_byte_timeout=100s \ -p between_bytes_timeout=5s \ -s malloc,3g"

Slide 248

Slide 248 text

Refresh content

Slide 249

Slide 249 text

vcl 4.0; acl refreshers { "172.18.0.0"/24; "localhost"; } backend default { .host = "nginx"; .port = "80"; } sub vcl_recv { if (req.method == "REFRESH") { if (client.ip !~ refreshers) { return (synth(405, "Method not allowed")); } set req.method = "GET"; set req.hash_always_miss = true; } } Refresh content Perform miss, fetch content, overwrite object

Slide 250

Slide 250 text

Refresh content curl -XREFRESH "http://example.com/products"

Slide 251

Slide 251 text

Tag-base invalidation

Slide 252

Slide 252 text

vmod_xkey

Slide 253

Slide 253 text

vcl 4.0; import xkey; acl purgers { "172.18.0.0"/24; } backend default { .host = "nginx"; .port = "80"; } sub vcl_recv { if (req.method == "PURGE") { if (client.ip !~ purgers) { return (synth(405, "Method now allowed")); } set req.http.n-gone = xkey.purge(req.http.key); return (synth(200, "Invalidated "+req.http.n-gone+" objects for key "+ req.http.key)); } if (req.method == "SOFTPURGE") { if (client.ip !~ purgers) { return (synth(405, "Method not allowed")); } set req.http.n-gone = xkey.softpurge(req.http.key); return (synth(200, "Invalidated "+req.http.n-gone+" objects via softpurge for key "+ req.http.key)); } }

Slide 254

Slide 254 text

'.PHP_EOL; Add “xkey” headers Register tags

Slide 255

Slide 255 text

Invalidate tags curl -XPURGE -H"key: b" "http://example.com/" PURGE / HTTP/1.1 Host: localhost User-Agent: curl/7.55.1 Accept: */* key:b

Slide 256

Slide 256 text

Header-based invalidation

Slide 257

Slide 257 text

Header-based invalidation If-Modified-Since: Tue, 14 Jun 2016 11:49:18 GMT Last-Modified: Tue, 14 Jun 2016 11:49:32 GMT Cache-Control: max-age=10 HTTP 200: OK HTTP 304: Not modified

Slide 258

Slide 258 text

Header-based invalidation If-None-Match: 57601698ae1e2 Etag: 57601698ae1e2 Cache-Control: max-age=10 HTTP 200: OK HTTP 304: Not modified

Slide 259

Slide 259 text

Grace mode

Slide 260

Slide 260 text

beresp.grace Keep servering stale object while fetching data from backend Avoid excessive request queueing

Slide 261

Slide 261 text

sub vcl_backend_response { set beresp.grace = 2h; } Grace

Slide 262

Slide 262 text

Cache-Control: public, max-age=100, s-maxage=3600, stale-while-revalidate=7200 Stale-While-Revalidate

Slide 263

Slide 263 text

sub vcl_backend_response { set beresp.grace = 6h; } Grace mode Monitoring & logging

Slide 264

Slide 264 text

Varnishstat Realtime statistics

Slide 265

Slide 265 text

No content

Slide 266

Slide 266 text

usage: varnishstat [-1lV] [-f field] [-t seconds|] [-n varnish_name] [-N filename] -1 # Print the statistics to stdout. -f field # Field inclusion glob # If it starts with '^' it is used as an exclusion list. -l # Lists the available fields to use with the -f option. -n varnish_name # The varnishd instance to get logs from. -N filename # Filename of a stale VSM instance. -t seconds| # Timeout before returning error on initial VSM connection. -V # Display the version number and exit. -x # Print statistics to stdout as XML. -j # Print statistics to stdout as JSON. Varnishstat usage

Slide 267

Slide 267 text

~# varnishstat -f MAIN.cache_hit -1 MAIN.cache_hit 13049135 5.39 Cache hits Varnishstat usage ~# varnishstat -f MAIN.cache_hit -j -1 { "timestamp": "2016-06-14T16:10:32", "MAIN.cache_hit": { "description": "Cache hits", "type": "MAIN", "flag": "c", "format": "i", "value": 13050992 } }

Slide 268

Slide 268 text

Varnishstat usage ~# varnishstat -f MAIN.n_object -f MAIN.n_lru_nuked -j { "timestamp": "2016-06-14T16:14:49", "MAIN.n_object": { "description": "object structs made", "type": "MAIN", "flag": "g", "format": "i", "value": 46295 }, "MAIN.n_lru_nuked": { "description": "Number of LRU nuked objects", "type": "MAIN", "flag": "g", "format": "i", "value": 0 } }

Slide 269

Slide 269 text

varnishstat -f MAIN.cache*

Slide 270

Slide 270 text

✓ Session ✓ Client ✓ Uptime ✓ Hit/miss ✓ Backend ✓ Fetch ✓ Threading ✓ Cache objects ✓ Memory ✓ Invalidation Varnishstat counters

Slide 271

Slide 271 text

VSL

Slide 272

Slide 272 text

Varnish Shared memory Logging

Slide 273

Slide 273 text

✓ In-memory logs ✓ Generated by varnishd ✓ 81 MB by default ✓ Customize with “-l” setting ✓ Varnishlog command ✓ Varnishtop command VSL

Slide 274

Slide 274 text

* << Request >> 10973258 - Begin req 10973257 rxreq - Timestamp Start: 1501507281.942533 0.000000 0.000000 - Timestamp Req: 1501507281.942533 0.000000 0.000000 - ReqStart 127.0.0.1 59753 - ReqMethod GET - ReqURL / - ReqProtocol HTTP/1.1 - ReqHeader Host: feryn.eu

Slide 275

Slide 275 text

Transactions

Slide 276

Slide 276 text

✓ Identifies TCP connection ✓ Contains multiple requests Transactions ✓ Client request ✓ Backend request ✓ ESI subrequest Session Request

Slide 277

Slide 277 text

No content

Slide 278

Slide 278 text

✓ VXID (default) ✓ Session ✓ Request ✓ Raw Transactions grouping

Slide 279

Slide 279 text

Example composition that will be monitored

Slide 280

Slide 280 text

varnishlog -i Begin,ReqUrl,Link,BereqURL

Slide 281

Slide 281 text

* << BeReq >> 98318 - Begin bereq 98317 fetch - BereqURL / * << BeReq >> 98320 - Begin bereq 98319 fetch - BereqURL /header * << Request >> 98319 - Begin req 98317 esi - ReqURL /header - Link bereq 98320 fetch * << BeReq >> 98322 - Begin bereq 98321 fetch - BereqURL /nav * << Request >> 98321 - Begin req 98317 esi - ReqURL /nav - Link bereq 98322 fetch * << Request >> 98317 - Begin req 98316 rxreq - ReqURL / - Link bereq 98318 fetch - Link req 98319 esi - Link req 98321 esi * << BeReq >> 98324 - Begin bereq 98323 fetch - BereqURL /footer * << Request >> 98323 - Begin req 98316 rxreq - ReqURL /footer - Link bereq 98324 fetch * << Session >> 98316 - Begin sess 0 HTTP/1 - Link req 98317 rxreq - Link req 98323 rxreq

Slide 282

Slide 282 text

varnishlog -i Begin,ReqUrl,Link,BereqURL -g session

Slide 283

Slide 283 text

* << Session >> 14 - Begin sess 0 HTTP/1 - Link req 65539 rxreq - Link req 65545 rxreq ** << Request >> 65539 -- Begin req 14 rxreq -- ReqURL / -- Link bereq 65540 fetch -- Link req 65541 esi -- Link req 65543 esi ** << Request >> 65545 -- Begin req 14 rxreq -- ReqURL /footer -- Link bereq 65546 fetch *** << BeReq >> 65540 --- Begin bereq 65539 fetch --- BereqURL / *** << Request >> 65541 --- Begin req 65539 esi --- ReqURL /header --- Link bereq 65542 fetch *** << Request >> 65543 --- Begin req 65539 esi --- ReqURL /nav --- Link bereq 65544 fetch *** << BeReq >> 65546 --- Begin bereq 65545 fetch --- BereqURL /footer **** << BeReq >> 65542 ---- Begin bereq 65541 fetch ---- BereqURL /header **** << BeReq >> 65544 ---- Begin bereq 65543 fetch ---- BereqURL /nav

Slide 284

Slide 284 text

Tags

Slide 285

Slide 285 text

Request tags

Slide 286

Slide 286 text

✓ ReqMethod ✓ ReqUrl ✓ ReqProtocol ✓ ReqHeader Request tags

Slide 287

Slide 287 text

- ReqStart 127.0.0.1 56312 - ReqMethod GET - ReqURL / - ReqProtocol HTTP/1.1 - ReqHeader Host: localhost - ReqHeader Connection: keep-alive - ReqHeader Upgrade-Insecure-Requests: 1 - ReqHeader User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/ 59.0.3071.115 Safari/537.36 - ReqHeader Accept: text/html,application/xhtml+xml,application/ xml;q=0.9,image/webp,image/apng,*/*;q=0.8 - ReqHeader Accept-Encoding: gzip, deflate, br - ReqHeader Accept-Language: nl,en-US;q=0.8,en;q=0.6 - ReqHeader X-Forwarded-For: 127.0.0.1

Slide 288

Slide 288 text

Response tags

Slide 289

Slide 289 text

✓ RespProtocol ✓ RespStatus ✓ RespReason ✓ RespHeader Response tags

Slide 290

Slide 290 text

- RespProtocol HTTP/1.1 - RespStatus 200 - RespReason OK - RespHeader Host: localhost - RespHeader Cache-Control: public, s-maxage=500 - RespHeader Date: Tue, 01 Aug 2017 08:56:44 GMT - RespHeader ETag: "c5afddc587599a72d467caca23e980bf" - RespHeader Vary: Accept-Language - RespHeader Content-Length: 3098 - RespHeader Content-Type: text/html; charset=UTF-8 - RespHeader X-Varnish: 32770 - RespHeader Age: 10

Slide 291

Slide 291 text

Backend request tags

Slide 292

Slide 292 text

✓ BereqMethod ✓ BereqUrl ✓ BereqProtocol ✓ BereqHeader Backend request tags

Slide 293

Slide 293 text

Backend response tags

Slide 294

Slide 294 text

✓ BerespProtocol ✓ BerespStatus ✓ BerespReason ✓ BerespHeader Backens response tags

Slide 295

Slide 295 text

Object tags

Slide 296

Slide 296 text

✓ ObjProtocol ✓ ObjStatus ✓ ObjReason ✓ ObjHeader Object tags

Slide 297

Slide 297 text

VCL tags

Slide 298

Slide 298 text

✓ VCL_Call ✓ VCL_Return ✓ VCL_Error ✓ VCL_Log ✓ VCL_Acl VCL tags

Slide 299

Slide 299 text

TTL tag

Slide 300

Slide 300 text

%s %d %d %d %d [ %d %d %u %u ] | | | | | | | | | | | | | | | | | +- Max-Age from Cache-Control header | | | | | | | +---- Expires header | | | | | | +------- Date header | | | | | +---------- Age (incl Age: header value) | | | | +--------------- Reference time for TTL | | | +------------------ Keep | | +--------------------- Grace | +------------------------ TTL +--------------------------- "RFC" or "VCL" TTL tag

Slide 301

Slide 301 text

-- TTL VCL 120 10 0 1501597242 ✓ TTL decided by the VCL ✓ 120 seconds cached ✓ 10 seconds grace time ✓ 0 seconds keep time ✓ Reference time: 1501597242
 (2017-08-01 14:20:42)

Slide 302

Slide 302 text

-- RFC 500 10 -1 1501598872 1501598872 1501598872 0 500 ✓ 500 seconds TTL (via headers) ✓ 10 seconds grace ✓ No keep value ✓ 2017-08-01 14:47:52 date, age & reference time ✓ Cache-control headers sets 500 second TTL

Slide 303

Slide 303 text

Begin tag

Slide 304

Slide 304 text

- Begin bereq 98317 fetch - Begin req 98317 esi - Begin req 98316 rxreq - Begin sess 0 HTTP/1

Slide 305

Slide 305 text

Link tag

Slide 306

Slide 306 text

- Link bereq 98312 fetch - Link req 98311 rxreq - Link req 98318 esi

Slide 307

Slide 307 text

Timestamp tag

Slide 308

Slide 308 text

%s: %f %f %f | | | | | | | +- Time since last timestamp | | +---- Time since start of work unit | +------- Absolute time of event +----------- Event label Timestamp tag

Slide 309

Slide 309 text

* << Request >> 65539 - Timestamp Start: 1501601912.758662 0.000000 0.000000 - Timestamp Req: 1501601912.758662 0.000000 0.000000 - Timestamp Fetch: 1501601912.806733 0.048071 0.048071 - Timestamp Process: 1501601912.806750 0.048088 0.000017 - Timestamp Resp: 1501601912.806787 0.048125 0.000037 ** << BeReq >> 65540 -- Timestamp Start: 1501601912.758753 0.000000 0.000000 -- Timestamp Bereq: 1501601912.758952 0.000199 0.000199 -- Timestamp Beresp: 1501601912.806677 0.047924 0.047725 -- Timestamp BerespBody: 1501601912.806749 0.047996 0.000072

Slide 310

Slide 310 text

Filtering output

Slide 311

Slide 311 text

✓ -i: include tags ✓ -I: include tags by regex ✓ -x: exclude tags ✓ -X: exclude by regex Filtering output

Slide 312

Slide 312 text

Include tags

Slide 313

Slide 313 text

varnishlog -i ReqUrl,VCL_call,VCL_return -g session varnishlog -i "ReqUrl,VCL_*" -g session

Slide 314

Slide 314 text

* << Session >> 252394 ** << Request >> 252395 -- ReqURL / -- VCL_call RECV -- VCL_return hash -- VCL_call HASH -- VCL_return lookup -- VCL_call HIT -- VCL_return deliver -- VCL_call DELIVER -- VCL_return deliver *** << Request >> 252397 --- ReqURL /header --- VCL_call RECV --- VCL_return hash --- VCL_call HASH --- VCL_return lookup --- VCL_call HIT --- VCL_return deliver --- VCL_call DELIVER --- VCL_return deliver *** << Request >> 252399 --- ReqURL /nav --- VCL_call RECV --- VCL_return hash --- VCL_call HASH --- VCL_return lookup --- VCL_call HIT --- VCL_return deliver --- VCL_call DELIVER --- VCL_return deliver *** << BeReq >> 252396 --- VCL_call BACKEND_FETCH --- VCL_return fetch --- VCL_call BACKEND_RESPONSE --- VCL_return deliver **** << BeReq >> 252398 ---- VCL_call BACKEND_FETCH ---- VCL_return fetch ---- VCL_call BACKEND_RESPONSE ---- VCL_return deliver **** << BeReq >> 252400 ---- VCL_call BACKEND_FETCH ---- VCL_return fetch ---- VCL_call BACKEND_RESPONSE ---- VCL_return deliver

Slide 315

Slide 315 text

Exclude tags

Slide 316

Slide 316 text

varnishlog -i "Req*" -x ReqHeader,ReqUnset

Slide 317

Slide 317 text

* << Request >> 314125 - ReqStart 127.0.0.1 64585 - ReqMethod GET - ReqURL / - ReqProtocol HTTP/1.1 - ReqAcct 476 0 476 311 0 311 * << Request >> 314126 - ReqStart 127.0.0.1 64585 - ReqMethod GET - ReqURL /footer - ReqProtocol HTTP/1.1 - ReqAcct 370 0 370 309 0 309

Slide 318

Slide 318 text

Include tags by regex

Slide 319

Slide 319 text

varnishlog -I "reqheader:Accept-Language" -i requrl

Slide 320

Slide 320 text

* << Request >> 374378 - ReqURL / - ReqHeader Accept-Language: nl,en-US;q=0.8,en;q=0.6 * << Request >> 374379 - ReqURL /footer - ReqHeader Accept-Language: nl,en-US;q=0.8,en;q=0.6

Slide 321

Slide 321 text

Exclude tags by regex

Slide 322

Slide 322 text

* << Request >> 374384 - ReqURL /footer - RespHeader Host: localhost - RespHeader X-Powered-By: PHP/7.0.15 - RespHeader Cache-Control: public, s-maxage=500 - RespHeader Date: Wed, 02 Aug 2017 11:27:21 GMT - RespHeader ETag: "d47ac09f5351f8f4c97c99ef5b3d2ecd" - RespHeader Vary: Accept-Language - RespHeader Content-Length: 80 - RespHeader Content-Type: text/html; charset=UTF-8 - RespHeader X-Varnish: 374384 15367 - RespHeader Age: 334 - RespHeader Via: 1.1 varnish-v4 - RespHeader Connection: keep-alive

Slide 323

Slide 323 text

varnishlog -i RespHeader,ReqUrl -X "RespHeader:(x|X)-"

Slide 324

Slide 324 text

* << Request >> 374384 - ReqURL /footer - RespHeader Host: localhost - RespHeader Cache-Control: public, s-maxage=500 - RespHeader Date: Wed, 02 Aug 2017 11:27:21 GMT - RespHeader ETag: "d47ac09f5351f8f4c97c99ef5b3d2ecd" - RespHeader Vary: Accept-Language - RespHeader Content-Length: 80 - RespHeader Content-Type: text/html; charset=UTF-8 - RespHeader Age: 334 - RespHeader Via: 1.1 varnish-v4 - RespHeader Connection: keep-alive

Slide 325

Slide 325 text

All-in-one

Slide 326

Slide 326 text

varnishlog -i "RespHeader,Req*" -X "RespHeader:(x|X)-" -I "timestamp:Resp" -x reqprotocol,reqacct -g request

Slide 327

Slide 327 text

* << Request >> 59383 - ReqStart 127.0.0.1 53195 - ReqMethod GET - ReqURL / - ReqHeader Host: localhost - ReqHeader Connection: keep-alive - ReqHeader Cache-Control: max-age=0 - ReqHeader Upgrade-Insecure-Requests: 1 - ReqHeader User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36 - ReqHeader Accept: text/ html,application/ xhtml+xml,application/xml;q=0.9,image/ webp,image/apng,*/*;q=0.8 - ReqHeader Accept-Encoding: gzip, deflate, br - ReqHeader Accept-Language: nl,en- US;q=0.8,en;q=0.6 - ReqHeader If-None-Match: W/"27f341f8e459dd35f1087c55351cacda" - ReqHeader X-Forwarded-For: 127.0.0.1 - ReqUnset Accept-Language: nl,en- US;q=0.8,en;q=0.6 - ReqHeader accept-language: nl - ReqHeader Surrogate-Capability: key=ESI/1.0 - ReqUnset Accept-Encoding: gzip, deflate, br - ReqHeader Accept-Encoding: gzip - RespHeader Host: localhost - RespHeader Cache-Control: public, s- maxage=500 - RespHeader Date: Wed, 02 Aug 2017 11:46:49 GMT - RespHeader ETag: "27f341f8e459dd35f1087c55351cacda" - RespHeader Vary: Accept-Language - RespHeader Content-Length: 3098 - RespHeader Content-Type: text/html; charset=UTF-8 - RespHeader Age: 152 - RespHeader Via: 1.1 varnish-v4 - RespHeader ETag: W/"27f341f8e459dd35f1087c55351cacda" - RespHeader Connection: keep-alive - Timestamp Resp: 1501674561.472358 0.000068 0.000020

Slide 328

Slide 328 text

✓ All response headers ✓ All tags that start with “Req” ✓ Exclude “x-“ response headers ✓ Include “response time” timestamp ✓ Exclude request protocol log lines, and the request accountancy log lines All-in-one

Slide 329

Slide 329 text

VSL queries

Slide 330

Slide 330 text

Filtering fields from all transactions Filtering transactions

Slide 331

Slide 331 text

{level}taglist:record-prefix[field]

Slide 332

Slide 332 text

ReqUrl eq ‘/‘ {level}taglist:record-prefix[field]

Slide 333

Slide 333 text

Timestamp:Resp[2] > 1.0 {level}taglist:record-prefix[field]

Slide 334

Slide 334 text

{2}Timestamp:Resp[2] > 1.0 {level}taglist:record-prefix[field]

Slide 335

Slide 335 text

varnishlog -i VCL_call,VCL_return -g request -q "ReqURL eq '/'"

Slide 336

Slide 336 text

* << Request >> 374400 - VCL_call RECV - VCL_return hash - VCL_call HASH - VCL_return lookup - VCL_call HIT - VCL_return deliver - VCL_call DELIVER - VCL_return deliver

Slide 337

Slide 337 text

varnishlog -i ReqUrl -q "VCL_call eq 'MISS' or VCL_call eq 'PASS'" varnishlog -i ReqUrl -I "Timestamp:Resp" -q "Timestamp:Resp[2] > 1.0"

Slide 338

Slide 338 text

Other options

Slide 339

Slide 339 text

varnishlog -n myVarnishInstance varnishlog -d varnishlog -w file varnishlog -r file varnishlog -A -a -w file varnishlog -i "ReqUrl,VCL_*" -D -a -A -w /var/log/varnish/custom.log - P /var/run/custom_varnishlog.pid

Slide 340

Slide 340 text

Varnishtop

Slide 341

Slide 341 text

Same syntax as varnishlog. Output is incremental

Slide 342

Slide 342 text

$ varnishtop -I reqheader:Accept-Language -q "ReqUrl eq '/'" list length 4 0.86 ReqHeader Accept-Language: en-US 0.80 ReqHeader Accept-Language: nl-NL,nl;q=0.8,en- US;q=0.6,en;q=0.4 0.54 ReqHeader Accept-Language: nl,en-US;q=0.8,en;q=0.6 0.39 ReqHeader Accept-Language: nl-BE

Slide 343

Slide 343 text

Common Varnishlog scenarios

Slide 344

Slide 344 text

Why didn’t Varnish serve this request from cache?

Slide 345

Slide 345 text

Request not cacheable

Slide 346

Slide 346 text

varnishlog -i "Req*,VCL*" -x ReqAcct,ReqStart -q "VCL_call eq 'PASS'"

Slide 347

Slide 347 text

* << Request >> 12 - ReqMethod GET - ReqURL / - ReqProtocol HTTP/1.1 - ReqHeader Host: localhost - ReqHeader User-Agent: curl/7.48.0 - ReqHeader Accept: */* - ReqHeader Cookie: myCookie=bla - ReqHeader X-Forwarded-For: 127.0.0.1 - VCL_call RECV - VCL_return pass - VCL_call HASH - VCL_return lookup - VCL_call PASS - VCL_return fetch - VCL_call DELIVER - VCL_return deliver

Slide 348

Slide 348 text

<< Request >> 32779 - ReqMethod POST - ReqURL / - ReqProtocol HTTP/1.1 - ReqHeader Host: localhost - ReqHeader User-Agent: curl/7.48.0 - ReqHeader Accept: */* - ReqHeader X-Forwarded-For: 127.0.0.1 - VCL_call RECV - VCL_return pass - VCL_call HASH - VCL_return lookup - VCL_call PASS - VCL_return fetch - VCL_call DELIVER - VCL_return deliver

Slide 349

Slide 349 text

* << Request >> 15 - ReqMethod GET - ReqURL / - ReqProtocol HTTP/1.1 - ReqHeader Host: localhost - ReqHeader Authorization: Basic dGhpanM6ZmVyeW4= - ReqHeader User-Agent: curl/7.48.0 - ReqHeader Accept: */* - ReqHeader X-Forwarded-For: 127.0.0.1 - VCL_call RECV - VCL_return pass - VCL_call HASH - VCL_return lookup - VCL_call PASS - VCL_return fetch - VCL_call DELIVER - VCL_return deliver

Slide 350

Slide 350 text

Response not cacheable

Slide 351

Slide 351 text

varnishlog -i ReqUrl,VCL_*,Beresp*,TTL -q "HitPass" -g request

Slide 352

Slide 352 text

* << Request >> 19010384 - ReqURL /my-url - VCL_call RECV - VCL_return hash - VCL_call HASH - VCL_return lookup - VCL_call PASS - VCL_return fetch - VCL_call DELIVER - VCL_return deliver ** << BeReq >> 19010385 -- VCL_call BACKEND_FETCH -- VCL_return fetch -- BerespProtocol HTTP/1.1 -- BerespStatus 200 -- BerespReason OK -- BerespHeader Date: Thu, 03 Aug 2017 08:15:22 GMT -- BerespHeader Server: Apache/ 2.4.10 (Debian) -- BerespHeader Last-Modified: Tue, 01 Aug 2017 07:21:00 GMT -- BerespHeader ETag: "5c0d-555abfd3f422f-gzip" -- BerespHeader Vary: Accept- Encoding -- BerespHeader Content-Encoding: gzip -- BerespHeader Cache-Control: max- age=0 -- BerespHeader Expires: Thu, 03 Aug 2017 08:15:22 GMT -- BerespHeader Content-Length: 7686 -- BerespHeader Content-Type: application/json -- TTL RFC 0 10 -1 1501748123 1501748123 1501748122 1501748122 0 -- VCL_call BACKEND_RESPONSE -- TTL VCL 120 10 0 1501748123 -- VCL_return deliver

Slide 353

Slide 353 text

* << Request >> 65551 - ReqURL /set-cookie - VCL_call RECV - VCL_return hash - VCL_call HASH - VCL_return lookup - VCL_call PASS - VCL_return fetch - VCL_call DELIVER - VCL_return deliver ** << BeReq >> 65552 -- VCL_call BACKEND_FETCH -- VCL_return fetch -- BerespProtocol HTTP/1.1 -- BerespStatus 200 -- BerespReason OK -- BerespHeader Cache-control: s-maxage=10 -- BerespHeader Set-Cookie: myCookie=bla -- BerespHeader Content-type: text/html; charset=UTF-8 -- BerespHeader Date: Thu, 03 Aug 2017 08:39:04 GMT -- TTL RFC 10 10 -1 1501749545 1501749545 1501749544 0 10 -- VCL_call BACKEND_RESPONSE -- TTL VCL 120 10 0 1501749545 -- VCL_return deliver

Slide 354

Slide 354 text

No content

Slide 355

Slide 355 text

Authentication

Slide 356

Slide 356 text

Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l

Slide 357

Slide 357 text

PHPSESSID: laken37oumlkrndoo91k3trlv5

Slide 358

Slide 358 text

vcl 4.0; sub vcl_recv { if (req.method == "PRI") { return (synth(405)); } if (req.method != "GET" && req.method != "HEAD" && req.method != "PUT" && req.method != "POST" && req.method != "TRACE" && req.method != "OPTIONS" && req.method != “PATCH" && req.method != "DELETE") { return (pipe); } if (req.method != "GET" && req.method != "HEAD") { return (pass); } if (req.http.Authorization || req.http.Cookie) { return (pass); } return (hash); }

Slide 359

Slide 359 text

No content

Slide 360

Slide 360 text

What if we could create cache variations for logged-in users?

Slide 361

Slide 361 text

How do you identify a logged-in user without accessing the backend every time?

Slide 362

Slide 362 text

Push session information from the server to the client

Slide 363

Slide 363 text

JSON Web Tokens

Slide 364

Slide 364 text

Marco Pivetta introduced me to JWT https://twitter.com/ocramius

Slide 365

Slide 365 text

JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOi JodHRwOlwvXC9sb2NhbGhvc3RcLyIsImlhdCI6MTUwODc0N zM2MywiZXhwIjoxNTA4NzUwOTYzLCJ1aWQiOjEsInVzZXJu YW1lIjoiVGhpanMifQ.EPemqBaH74Sbs0bZqAaR8i7uVYO3 89VOlJvWC3ocL7g ✓3 parts ✓Dot separated ✓Base64 encoded JSON ✓Header ✓Payload ✓Signature (HMAC with secret)

Slide 366

Slide 366 text

eyJpc3MiOiJodHRwOlwvXC 9sb2NhbGhvc3RcLyIsImlh dCI6MTUwODc0NzM2MywiZX hwIjoxNTA4NzUwOTYzLCJ1 aWQiOjEsInVzZXJuYW1lIj oiVGhpanMifQ { "alg": "HS256", "typ": "JWT" } { "iss": "http://localhost/", "iat": 1508746861, "exp": 1508750461, "uid": 1, "username": "Thijs" } HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret ) eyJ0eXAiOiJKV1QiLCJhb GciOiJIUzI1NiJ9 EPemqBaH74Sbs0bZqAaR8i 7uVYO389VOlJvWC3ocL7g

Slide 367

Slide 367 text

https://jwt.io

Slide 368

Slide 368 text

No content

Slide 369

Slide 369 text

JWT Cookie:jwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9. eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3RcLyIsImlhdCI6MTUwOD c0NzM2MywiZXhwIjoxNTA4NzUwOTYzLCJ1aWQiOjEsInVzZXJuYW1l IjoiVGhpanMifQ.EPemqBaH74Sbs0bZqAaR8i7uVYO389VOlJvWC3o cL7g ✓Stored in a cookie ✓Can be validated by Varnish ✓Payload can be processed by any language (e.g. Javascript)

Slide 370

Slide 370 text

Application creates JWT alongside the regular session

Slide 371

Slide 371 text

Template loads stateful data from JWT in Javascript

Slide 372

Slide 372 text

{ "iss": "http://localhost/", "iat": 1508746861, "exp": 1508750461, "uid": 1, "username": "Thijs" } Issuer

Slide 373

Slide 373 text

{ "iss": "http://localhost/", "iat": 1508746861, "exp": 1508750461, "uid": 1, "username": "Thijs" } Issued at

Slide 374

Slide 374 text

{ "iss": "http://localhost/", "iat": 1508746861, "exp": 1508750461, "uid": 1, "username": "Thijs" } Expires at

Slide 375

Slide 375 text

{ "iss": "http://localhost/", "iat": 1508746861, "exp": 1508750461, "uid": 1, "username": "Thijs" } User ID

Slide 376

Slide 376 text

{ "iss": "http://localhost/", "iat": 1508746861, "exp": 1508750461, "uid": 1, "username": "Thijs" } Username

Slide 377

Slide 377 text

{ "iss": "http://localhost/", "iat": 1508746861, "exp": 1508750461, "uid": 1, "username": "Thijs" } Let Javascript parse this one

Slide 378

Slide 378 text

User Varnish Server Issues JWT Validates JWT Reads JWT Knows secret key Knows secret key Does not know secret key

Slide 379

Slide 379 text

Using vmod_digest for HMAC & base64

Slide 380

Slide 380 text

vcl 4.0; import std; import var; import cookie; import digest; backend default { .host = "nginx"; .port = "80"; } sub vcl_recv { if (req.http.Cookie) { cookie.parse(req.http.cookie); cookie.filter_except("jwt_cookie"); set req.http.cookie = cookie.get_string(); if (req.http.Cookie ~ "^\s*$") { unset req.http.Cookie; } } if ((req.method != "GET" && req.method != "HEAD") || req.http.Authorization) { return (pass); } call jwt; return(hash); }

Slide 381

Slide 381 text

vcl 4.0; import std; import var; import cookie; import digest; backend default { .host = "nginx"; .port = "80"; } sub vcl_recv { if (req.http.Cookie) { cookie.parse(req.http.cookie); cookie.filter_except("jwt_cookie"); set req.http.cookie = cookie.get_string(); if (req.http.Cookie ~ "^\s*$") { unset req.http.Cookie; } } if ((req.method != "GET" && req.method != "HEAD") || req.http.Authorization) { return (pass); } call jwt; return(hash); }

Slide 382

Slide 382 text

vcl 4.0; import std; import var; import cookie; import digest; backend default { .host = "nginx"; .port = "80"; } sub vcl_recv { if (req.http.Cookie) { cookie.parse(req.http.cookie); cookie.filter_except("jwt_cookie"); set req.http.cookie = cookie.get_string(); if (req.http.Cookie ~ "^\s*$") { unset req.http.Cookie; } } if ((req.method != "GET" && req.method != "HEAD") || req.http.Authorization) { return (pass); } call jwt; return(hash); }

Slide 383

Slide 383 text

vcl 4.0; import std; import var; import cookie; import digest; backend default { .host = "nginx"; .port = "80"; } sub vcl_recv { if (req.http.Cookie) { cookie.parse(req.http.cookie); cookie.filter_except("jwt_cookie"); set req.http.cookie = cookie.get_string(); if (req.http.Cookie ~ "^\s*$") { unset req.http.Cookie; } } if ((req.method != "GET" && req.method != "HEAD") || req.http.Authorization) { return (pass); } call jwt; return(hash); }

Slide 384

Slide 384 text

vcl 4.0; import std; import var; import cookie; import digest; backend default { .host = "nginx"; .port = "80"; } sub vcl_recv { if (req.http.Cookie) { cookie.parse(req.http.cookie); cookie.filter_except("jwt_cookie"); set req.http.cookie = cookie.get_string(); if (req.http.Cookie ~ "^\s*$") { unset req.http.Cookie; } } if ((req.method != "GET" && req.method != "HEAD") || req.http.Authorization) { return (pass); } call jwt; return(hash); }

Slide 385

Slide 385 text

sub vcl_synth { #301 & 302 synths should be actual redirects if (resp.status == 301 || resp.status == 302) { set resp.http.location = resp.reason; set resp.reason = "Moved"; return (deliver); } }

Slide 386

Slide 386 text

sub jwt { var.set("key",std.fileread("/etc/varnish/jwt.key")); std.log("Ready to perform some JWT magic"); if(cookie.isset("jwt_cookie")) { var.set("token", cookie.get("jwt_cookie")); var.set("header", regsub(var.get("token"),"([^\.]+)\.[^\.]+\.[^\.]+","\1")); var.set("type", regsub(digest.base64url_decode(var.get("header")),{"^.*?"typ"\s*: \s*"(\w+)".*?$"},"\1")); var.set("algorithm", regsub(digest.base64url_decode(var.get("header")), {"^.*?"alg"\s*:\s*"(\w+)".*?$"},"\1")); if(var.get("type") == "JWT" && var.get("algorithm") == "HS256") { var.set("rawPayload",regsub(var.get("token"),"[^\.]+\.([^\.]+)\.[^\.]+$","\1")); var.set("signature",regsub(var.get("token"),"^[^\.]+\.[^\.]+\.([^\.]+)$","\1")); var.set("currentSignature",digest.base64url_nopad_hex(digest.hmac_sha256(var.get("key"), var.get("header") + "." + var.get("rawPayload")))); var.set("payload", digest.base64url_decode(var.get("rawPayload"))); var.set("exp",regsub(var.get("payload"),{"^.*?"exp"\s*:\s*([0-9]+).*?$"},"\1")); var.set("userId",regsub(var.get("payload"),{"^.*?"uid"\s*:\s*([0-9]+).*? $"},"\1")); if(var.get("userId") ~ "^\d+$") { if(std.time(var.get("exp"),now) >= now) { if(var.get("signature") == var.get("currentSignature")) { set req.http.X-Login="true"; } else { std.log("JWT: signature doesn't match. Received: " + var.get("signature") + ", expected: " + var.get("currentSignature"));

Slide 387

Slide 387 text

sub jwt { var.set("key",std.fileread("/etc/varnish/jwt.key")); std.log("Ready to perform some JWT magic"); if(cookie.isset("jwt_cookie")) { var.set("token", cookie.get("jwt_cookie")); var.set("header", regsub(var.get("token"),"([^\.]+)\.[^\.]+\.[^\.]+","\1")); var.set("type", regsub(digest.base64url_decode(var.get("header")),{"^.*?"typ"\s*: \s*"(\w+)".*?$"},"\1")); var.set("algorithm", regsub(digest.base64url_decode(var.get("header")), {"^.*?"alg"\s*:\s*"(\w+)".*?$"},"\1")); if(var.get("type") == "JWT" && var.get("algorithm") == "HS256") { var.set("rawPayload",regsub(var.get("token"),"[^\.]+\.([^\.]+)\.[^\.]+$","\1")); var.set("signature",regsub(var.get("token"),"^[^\.]+\.[^\.]+\.([^\.]+)$","\1")); var.set("currentSignature",digest.base64url_nopad_hex(digest.hmac_sha256(var.get("key"), var.get("header") + "." + var.get("rawPayload")))); var.set("payload", digest.base64url_decode(var.get("rawPayload"))); var.set("exp",regsub(var.get("payload"),{"^.*?"exp"\s*:\s*([0-9]+).*?$"},"\1")); var.set("userId",regsub(var.get("payload"),{"^.*?"uid"\s*:\s*([0-9]+).*? $"},"\1")); if(var.get("userId") ~ "^\d+$") { if(std.time(var.get("exp"),now) >= now) { if(var.get("signature") == var.get("currentSignature")) { set req.http.X-Login="true"; } else { std.log("JWT: signature doesn't match. Received: " + var.get("signature") + ", expected: " + var.get("currentSignature"));

Slide 388

Slide 388 text

var.set("header", regsub(var.get("token"),"([^\.]+)\.[^\.]+\.[^\.]+","\1")); First chunk contains the header

Slide 389

Slide 389 text

var.set("type", regsub(digest.base64url_decode(var.get("header")), {"^.*?"typ"\s*:\s*"(\w+)".*?$"},"\1")); Get type field from header

Slide 390

Slide 390 text

var.set("algorithm", regsub(digest.base64url_decode(var.get("header")), {"^.*?"alg"\s*:\s*"(\w+)".*?$"},"\1")); Get algorithm field from header

Slide 391

Slide 391 text

sub jwt { var.set("key",std.fileread("/etc/varnish/jwt.key")); std.log("Ready to perform some JWT magic"); if(cookie.isset("jwt_cookie")) { var.set("token", cookie.get("jwt_cookie")); var.set("header", regsub(var.get("token"),"([^\.]+)\.[^\.]+\.[^\.]+","\1")); var.set("type", regsub(digest.base64url_decode(var.get("header")),{"^.*?"typ"\s*: \s*"(\w+)".*?$"},"\1")); var.set("algorithm", regsub(digest.base64url_decode(var.get("header")), {"^.*?"alg"\s*:\s*"(\w+)".*?$"},"\1")); if(var.get("type") == "JWT" && var.get("algorithm") == "HS256") { var.set("rawPayload",regsub(var.get("token"),"[^\.]+\.([^\.]+)\.[^\.]+$","\1")); var.set("signature",regsub(var.get("token"),"^[^\.]+\.[^\.]+\.([^\.]+)$","\1")); var.set("currentSignature",digest.base64url_nopad_hex(digest.hmac_sha256(var.get("key"), var.get("header") + "." + var.get("rawPayload")))); var.set("payload", digest.base64url_decode(var.get("rawPayload"))); var.set("exp",regsub(var.get("payload"),{"^.*?"exp"\s*:\s*([0-9]+).*?$"},"\1")); var.set("userId",regsub(var.get("payload"),{"^.*?"uid"\s*:\s*([0-9]+).*? $"},"\1")); if(var.get("userId") ~ "^\d+$") { if(std.time(var.get("exp"),now) >= now) { if(var.get("signature") == var.get("currentSignature")) { set req.http.X-Login="true"; } else { std.log("JWT: signature doesn't match. Received: " + var.get("signature") + ", expected: " + var.get("currentSignature"));

Slide 392

Slide 392 text

var.set(“rawPayload",regsub(var.get("token"),"[^\.]+\.([^\.]+)\.[^\.]+ $","\1")); var.set("payload", digest.base64url_decode(var.get("rawPayload"))); Second chunk contains the raw payload Base64 decode to get JSON payload

Slide 393

Slide 393 text

var.set("signature",regsub(var.get("token"),"^[^\.]+\.[^\.]+\.([^\.]+) $","\1")); var.set("currentSignature",digest.base64url_nopad_hex(digest.hmac_sha256(var .get("key"),var.get("header") + "." + var.get("rawPayload")))); Third chunk contains the raw signature Build signature using key, header & payload

Slide 394

Slide 394 text

var.set("exp",regsub(var.get("payload"),{"^.*?"exp"\s*:\s*([0-9]+).*? $"},"\1")); var.set("userId",regsub(var.get("payload"),{"^.*?"uid"\s*:\s*([0-9]+).*? $"},"\1")); Extract expiration time from JSON payload Extract user ID from JSON payload

Slide 395

Slide 395 text

var.get("header") + "." + var.get("rawPayload")))); var.set("payload", digest.base64url_decode(var.get("rawPayload"))); var.set("exp",regsub(var.get("payload"),{"^.*?"exp"\s*:\s*([0-9]+).*?$"},"\1")); var.set("userId",regsub(var.get("payload"),{"^.*?"uid"\s*:\s*([0-9]+).*? $"},"\1")); if(var.get("userId") ~ "^\d+$") { if(std.time(var.get("exp"),now) >= now) { if(var.get("signature") == var.get("currentSignature")) { set req.http.X-Login="true"; } else { std.log("JWT: signature doesn't match. Received: " + var.get("signature") + ", expected: " + var.get("currentSignature")); } } else { std.log("JWT: token has expired"); } } else { std.log("UserId '"+ var.get("userId") +"', is not numeric"); } } else { std.log("JWT: type is not JWT or algorithm is not HS256"); } std.log("JWT processing finished. UserId: " + var.get("userId") + ". X-Login: " + req.http.X-login); } if(req.url ~ "^/(\?[^\?]+)?$" && req.http.X-Login != "true") { return(synth(302,"/post.php")); } }

Slide 396

Slide 396 text

var.get("header") + "." + var.get("rawPayload")))); var.set("payload", digest.base64url_decode(var.get("rawPayload"))); var.set("exp",regsub(var.get("payload"),{"^.*?"exp"\s*:\s*([0-9]+).*?$"},"\1")); var.set("userId",regsub(var.get("payload"),{"^.*?"uid"\s*:\s*([0-9]+).*? $"},"\1")); if(var.get("userId") ~ "^\d+$") { if(std.time(var.get("exp"),now) >= now) { if(var.get("signature") == var.get("currentSignature")) { set req.http.X-Login="true"; } else { std.log("JWT: signature doesn't match. Received: " + var.get("signature") + ", expected: " + var.get("currentSignature")); } } else { std.log("JWT: token has expired"); } } else { std.log("UserId '"+ var.get("userId") +"', is not numeric"); } } else { std.log("JWT: type is not JWT or algorithm is not HS256"); } std.log("JWT processing finished. UserId: " + var.get("userId") + ". X-Login: " + req.http.X-login); } if(req.url ~ "^/(\?[^\?]+)?$" && req.http.X-Login != "true") { return(synth(302,"/post.php")); } }

Slide 397

Slide 397 text

Cache variations

Slide 398

Slide 398 text

Vary: X-Login Vary header sent by application X-Login header set by Varnish Creates cache variations in Varnish

Slide 399

Slide 399 text

Write cacheable applications

Slide 400

Slide 400 text

✓ Cache-control ✓ Content negotiation ✓ Cache variations ✓ Conditional requests ✓ Content composition ✓ JWT authentication Checklist

Slide 401

Slide 401 text

https://feryn.eu https://twitter.com/ThijsFeryn https://instagram.com/ThijsFeryn

Slide 402

Slide 402 text

No content