Slide 1

Slide 1 text

By Thijs Feryn Varnish in -depth training

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

Saturated market

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Heavy load

Slide 11

Slide 11 text

Mo money Mo servers

Slide 12

Slide 12 text

Identify slowest parts

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

Reduce the impact of the code on the server

Slide 16

Slide 16 text

Not just raw speed

Slide 17

Slide 17 text

Scale

Slide 18

Slide 18 text

Refactor slow code Write fast code

Slide 19

Slide 19 text

Optimize database

Slide 20

Slide 20 text

Improve the API call

Slide 21

Slide 21 text

Optimize runtime

Slide 22

Slide 22 text

After a while you hit the limits

Slide 23

Slide 23 text

Cache

Slide 24

Slide 24 text

Don’t recompute if the data hasn’t changed

Slide 25

Slide 25 text

What can you cache? Byte code Database output External services Files from disk Pages

Slide 26

Slide 26 text

Caching is not a compensation for poor code

Slide 27

Slide 27 text

Caching is an essential architectural strategy

Slide 28

Slide 28 text

Normally User Server

Slide 29

Slide 29 text

With Varnish User Varnish Server

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Hi, I’m Thijs

Slide 32

Slide 32 text

I’m @ThijsFeryn on Twitter

Slide 33

Slide 33 text

I’m an Evangelist At

Slide 34

Slide 34 text

I’m an Evangelist At

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

Varnish

Slide 37

Slide 37 text

Cache?

Slide 38

Slide 38 text

Loadbalancer?

Slide 39

Slide 39 text

Proxy?

Slide 40

Slide 40 text

Web application firewall?

Slide 41

Slide 41 text

HTTP accelerator

Slide 42

Slide 42 text

Regular proxy User Proxy Server Office

Slide 43

Slide 43 text

With reverse proxy User Proxy Server Datacenter

Slide 44

Slide 44 text

How does it work?

Slide 45

Slide 45 text

varnishd VCL file Compiled & linked as shared object Client HTTP Backend HTTP Memory Logs Writes verbose “transaction” logs to memory varnishlog varnishtop Read memory logs, displays information varnishncsa varnishadm Allow basic management tasks Custom protocol

Slide 46

Slide 46 text

Install & configure

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

$ apt-cache policy varnish varnish: Installed: 6.0.0-1~stretch Candidate: 6.0.0-1~stretch Version table: *** 6.0.0-1~stretch 500 500 https://packagecloud.io/varnishcache/varnish60/debian stretch/main amd64 Packages 100 /var/lib/dpkg/status 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 50

Slide 50 text

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

Slide 51

Slide 51 text

$ curl -s https://packagecloud.io/install/repositories/ varnishcache/varnish60/script.rpm.sh | sudo bash $ yum install varnish RHEL & CentOS

Slide 52

Slide 52 text

Config file

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

SysV Systemd Ubuntu/Debian /etc/default/varnish /etc/systemd/system/varnish.service RHEL/CentOS /etc/sysconfig/varnish /etc/varnish/varnish.params

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Startup options

Slide 57

Slide 57 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 58

Slide 58 text

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

Slide 59

Slide 59 text

DAEMON_OPTS="-a :80 \ -T localhost:6082 \ -f /etc/varnish/default.vcl \ -S /etc/varnish/secret \ -s malloc,256m" Ubuntu Trusty without systemd

Slide 60

Slide 60 text

vim /etc/default/varnish service varnish reload Reload with sysv

Slide 61

Slide 61 text

-a: binding address & port -T: admin binding -f: VCL file -S: secret file -s: storage -j: jailing -l: shared memory log size -t: default TTL -p: runtime parameters Options

Slide 62

Slide 62 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 feature=+http2 \ -p connect_timeout=20s \ -p first_byte_timeout=100s \ -p between_bytes_timeout=5s \ -s malloc,3g"

Slide 63

Slide 63 text

The backend

Slide 64

Slide 64 text

80

Slide 65

Slide 65 text

Point of entry? Bind to 80 Otherwise 6081

Slide 66

Slide 66 text

8080 HTTP-ALT

Slide 67

Slide 67 text

Listen 8080 Apache ports.conf vhost

Slide 68

Slide 68 text

listen 8080; Nginx vhost

Slide 69

Slide 69 text

✓In VCL file ✓In config file using "-b" Link backend to Varnish

Slide 70

Slide 70 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 71

Slide 71 text

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

Slide 72

Slide 72 text

Typical setups

Slide 73

Slide 73 text

All-in-one User Varnish (80) Nginx (8080)

Slide 74

Slide 74 text

Dedicated Varnish User Varnish (80) Nginx (80)

Slide 75

Slide 75 text

Multiple webservers User Varnish (80) Nginx (8080) Varnish (80) Nginx (8080) KeepAliveD

Slide 76

Slide 76 text

Multiple webservers & dedicated Varnish User Varnish (80) KeepAliveD Varnish (80) Nginx (80) Nginx (80)

Slide 77

Slide 77 text

Loadbalancing User Varnish (80) KeepAliveD Varnish (80) Nginx (80) Nginx (80)

Slide 78

Slide 78 text

HAProxy loadbalancers User HAProxy (80) Varnish (6081) KeepAliveD HAProxy (80) Varnish (6081) Nginx (80) Nginx (80)

Slide 79

Slide 79 text

User HAProxy (80) KeepAliveD HAProxy (80) Varnish (80) Nginx (8080) Varnish (80) Nginx (8080) HAProxy loadbalancers, Varnish on webserver

Slide 80

Slide 80 text

Share the cache

Slide 81

Slide 81 text

Share the cache User Nginx (80) Nginx (80) 1 2 3 4 5 6 HAProxy (80) Varnish ( 6080 & 6081) HAProxy (80) Varnish ( 6080 & 6081)

Slide 82

Slide 82 text

Share the cache 2 User HAProxy (80) Varnish ( 6080 & 6081) Nginx (80) Nginx (80) 1 5 3 4 2 6 HAProxy (80) Varnish ( 6080 & 6081)

Slide 83

Slide 83 text

vcl 4.0; import std; backend nginx { .host = "nginx"; .port = "80"; } backend nginx2 { .host = "nginx2"; .port = "80"; } backend varnish { .host = "varnish"; .port = "6080"; } backend varnish2 { .host = "varnish2"; .port = "6080"; } sub vcl_recv { if(server.hostname == "varnish") { if(std.port(server.ip) == 6080) { set req.backend_hint = nginx; set req.http.x-esi = "yes"; } else { set req.backend_hint = varnish2; set req.http.x-esi = "no"; } } else { if(std.port(server.ip) == 6080) { set req.backend_hint = nginx2; set req.http.x-esi = "yes"; } else { set req.backend_hint = varnish; set req.http.x-esi = "no"; } } } sub vcl_backend_response { if (bereq.http.x-esi == "yes" && beresp.http.Surrogate-Control ~ "ESI/1.0") { unset beresp.http.Surrogate-Control; set beresp.do_esi = true; } }

Slide 84

Slide 84 text

sub vcl_recv { if(server.hostname == "varnish") { if(std.port(server.ip) == 6080) { set req.backend_hint = nginx; set req.http.x-esi = "yes"; } else { set req.backend_hint = varnish2; set req.http.x-esi = "no"; } } else { if(std.port(server.ip) == 6080) { set req.backend_hint = nginx2; set req.http.x-esi = "yes"; } else { set req.backend_hint = varnish; set req.http.x-esi = "no"; } } }

Slide 85

Slide 85 text

What about TLS/SSL?

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

How do you know if it was HTTP or HTTPS?

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

No content

Slide 92

Slide 92 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 93

Slide 93 text

Demo setup

Slide 94

Slide 94 text

https://github.com/ThijsFeryn/ varnishtraining

Slide 95

Slide 95 text

$ docker-compose up $ docker-compose down $ docker-compose up -d $ docker-compose logs $ docker-compose logs -f $ docker-compose logs varnish $ docker-compose logs -f varnish

Slide 96

Slide 96 text

Demo setup HAProxy (80 & 443) Varnish (6080, 6081, 6082, 6083) Nginx (80) PHP-FPM (9000) Varnish 2 (6080, 6081, 6082, 6083) Nginx 2 (80) PHP-FPM 2 (9000) MySQL (3306) Redis (6379)

Slide 97

Slide 97 text

✓sflive-varnish2: 172.18.0.10 ✓sflive-varnish: 172.18.0.8 ✓sflive-nginx2: 172.18.0.6 ✓sflive-nginx: 172.18.0.2 ✓sflive-php2: 172.18.0.5 ✓sflive-php: 172.18.0.4 ✓sflive-haproxy: 172.18.0.3 ✓sflive-redis: 172.18.0.7 ✓sflive-mysql: 172.18.0.9 Demo setup IPs

Slide 98

Slide 98 text

What’s the real IP? 172.18.0.1 ?

Slide 99

Slide 99 text

HAProxy 172.18.0.3 Varnish 172.18.0.8 Nginx Public 172.18.0.1

Slide 100

Slide 100 text

X-Forwarded-For

Slide 101

Slide 101 text

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

Slide 102

Slide 102 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 103

Slide 103 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 104

Slide 104 text

PROXY Protocol

Slide 105

Slide 105 text

Add original IP as TCP preamble

Slide 106

Slide 106 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 107

Slide 107 text

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

Slide 108

Slide 108 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 109

Slide 109 text

Let's have a look at the default example

Slide 110

Slide 110 text

There are rules

Slide 111

Slide 111 text

No content

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

Idempotence Execute multiple times Result doesn't change

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

State

Slide 116

Slide 116 text

State ~ user specific data Cookies Auth headers

Slide 117

Slide 117 text

About cookies

Slide 118

Slide 118 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 119

Slide 119 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 120

Slide 120 text

Time To Live

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 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 124

Slide 124 text

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

Slide 125

Slide 125 text

Age Age: 10 How old is the cached object?

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

Don’t set the Age header yourself!

Slide 128

Slide 128 text

Conditional requests

Slide 129

Slide 129 text

Only fetch payload that has changed

Slide 130

Slide 130 text

Otherwise: HTTP/1.1 304 Not Modified

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 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 134

Slide 134 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 135

Slide 135 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 136

Slide 136 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 137

Slide 137 text

Store etag or modification date

Slide 138

Slide 138 text

Validate early on

Slide 139

Slide 139 text

Exit quickly

Slide 140

Slide 140 text

Varnish can revalidate aynchronously

Slide 141

Slide 141 text

And serve stale data while that happens

Slide 142

Slide 142 text

Grace mode

Slide 143

Slide 143 text

Effective TTL = TTL + grace

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

Variations

Slide 146

Slide 146 text

Cache is not the same for everyone

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

Let's test some HTTP behavior

Slide 150

Slide 150 text

The flow

Slide 151

Slide 151 text

No content

Slide 152

Slide 152 text

No content

Slide 153

Slide 153 text

Varnish Configuration Language

Slide 154

Slide 154 text

DSL compiled and linked as shared object

Slide 155

Slide 155 text

/etc/varnish/default.vcl

Slide 156

Slide 156 text

Hooks & subroutines

Slide 157

Slide 157 text

VCL Recv VCL Hash VCL Miss VCL Hit VCL Backend Response VCL Deliver VCL Purge VCL Error VCL Pipe VCL Pass VCL Synth VCL Backend Error

Slide 158

Slide 158 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 159

Slide 159 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 160

Slide 160 text

Actions

Slide 161

Slide 161 text

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

Slide 162

Slide 162 text

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

Slide 163

Slide 163 text

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

Slide 164

Slide 164 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 165

Slide 165 text

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

Slide 166

Slide 166 text

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

Slide 167

Slide 167 text

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

Slide 168

Slide 168 text

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

Slide 169

Slide 169 text

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

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

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

Slide 172

Slide 172 text

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

Slide 173

Slide 173 text

Typical flows

Slide 174

Slide 174 text

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

Slide 175

Slide 175 text

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

Slide 176

Slide 176 text

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

Slide 177

Slide 177 text

Objects

Slide 178

Slide 178 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 179

Slide 179 text

Variables

Slide 180

Slide 180 text

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

Slide 181

Slide 181 text

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

Slide 182

Slide 182 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 183

Slide 183 text

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

Slide 184

Slide 184 text

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

Slide 185

Slide 185 text

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

Slide 186

Slide 186 text

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

Slide 187

Slide 187 text

Default behaviour

Slide 188

Slide 188 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 189

Slide 189 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 190

Slide 190 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 191

Slide 191 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 192

Slide 192 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 193

Slide 193 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 194

Slide 194 text

Minimal VCL

Slide 195

Slide 195 text

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

Slide 196

Slide 196 text

vcl 4.0; backend default { .host = "127.0.0.1"; .port = "80"; .max_connections = 300; .first_byte_timeout = 300s; .connect_timeout = 5s; .between_bytes_timeout = 2s; } More backend work

Slide 197

Slide 197 text

vcl 4.0; backend default { .host = "127.0.0.1"; .port = "80"; .max_connections = 300; .first_byte_timeout = 300s; .connect_timeout = 5s; .between_bytes_timeout = 2s; .probe = { .url = "/"; .interval = 5s; .timeout = 1s; .window = 5; .threshold = 3; } } Even more backend work

Slide 198

Slide 198 text

vcl 4.0; backend default { .host = "127.0.0.1"; .port = "80"; .max_connections = 300; .first_byte_timeout = 300s; .connect_timeout = 5s; .between_bytes_timeout = 2s; .probe = { .request = "HEAD / HTTP/1.1" "Host: localhost" "Connection: close" "User-Agent: Varnish Health Probe"; .interval = 5s; .timeout = 1s; .window = 5; .threshold = 3; } } Even more backend work

Slide 199

Slide 199 text

VMOD

Slide 200

Slide 200 text

Varnish modules

Slide 201

Slide 201 text

Written in C

Slide 202

Slide 202 text

Exposes new VCL objects

Slide 203

Slide 203 text

Added functionality

Slide 204

Slide 204 text

vcl 4.0; import directors; import std; import cookie; sub vcl_init { new vdir = directors.round_robin(); vdir.add_backend(…); vdir.add_backend(…); } VMOD

Slide 205

Slide 205 text

vmod_std

Slide 206

Slide 206 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 207

Slide 207 text

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

Slide 208

Slide 208 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 209

Slide 209 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 210

Slide 210 text

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

Slide 211

Slide 211 text

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

Slide 212

Slide 212 text

sub vcl_recv { return(synth(200,std.real2integer(std.random(1,10),0))); } std.random & std.real2integer

Slide 213

Slide 213 text

sub vcl_recv { return(synth(200,std.toupper("yes") + " " + std.tolower("YES"))); } std.toupper & std.tolower

Slide 214

Slide 214 text

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

Slide 215

Slide 215 text

Load balancing

Slide 216

Slide 216 text

vcl 4.0; import directors; backend server1 { .host = “1.2.3.4”; .port = "80"; .max_connections = 300; .first_byte_timeout = 300s; .connect_timeout = 5s; .between_bytes_timeout = 2s; .probe = { .request = "HEAD / HTTP/1.1" "Host: localhost" "Connection: close" "User-Agent: Varnish Health Probe"; .interval = 5s; .timeout = 1s; .window = 5; .threshold = 3; } } Loadbalancing: server 1

Slide 217

Slide 217 text

backend server2 { .host = “1.2.3.5”; .port = "80"; .max_connections = 300; .first_byte_timeout = 300s; .connect_timeout = 5s; .between_bytes_timeout = 2s; .probe = { .request = "HEAD / HTTP/1.1" "Host: localhost" "Connection: close" "User-Agent: Varnish Health Probe"; .interval = 5s; .timeout = 1s; .window = 5; .threshold = 3; } } Loadbalancing: server 2

Slide 218

Slide 218 text

sub vcl_init { new vdir = directors.round_robin(); vdir.add_backend(server1); vdir.add_backend(server2); } sub vcl_recv { set req.backend_hint = vdir.backend(); } Loadbalancing: round robin

Slide 219

Slide 219 text

sub vcl_init { new vdir = directors.random(); vdir.add_backend(server1,2); vdir.add_backend(server2,3); } sub vcl_recv { set req.backend_hint = vdir.backend(); } Loadbalancing: random

Slide 220

Slide 220 text

sub vcl_init { new vdir = directors.fallback(); vdir.add_backend(server1); vdir.add_backend(server2); } sub vcl_recv { set req.backend_hint = vdir.backend(); } Loadbalancing: fallback

Slide 221

Slide 221 text

sub vcl_init { new vdir = directors.hash(); vdir.add_backend(server1); vdir.add_backend(server2); } sub vcl_recv { set req.backend_hint = vdir.backend(req.url); } Loadbalancing: URL hash

Slide 222

Slide 222 text

sub vcl_init { new vdir = directors.hash(); vdir.add_backend(server1); vdir.add_backend(server2); } sub vcl_recv { set req.backend_hint = vdir.backend(client.identity); } Loadbalancing: IP hash

Slide 223

Slide 223 text

sub vcl_recv { if(req.url ~ “^/products”) { set req.backend_hint = server1; } else { set req.backend_hint = server2; } } Conditional loadbalancing

Slide 224

Slide 224 text

Install other VMODs

Slide 225

Slide 225 text

apt-get install -y varnish varnish-dev build-essential curl -L -O https://github.com/varnish/varnish-modules/archive/ master.zip unzip master.zip cd varnish-modules-master ./bootstrap ./configure make make install Install official VMODs

Slide 226

Slide 226 text

apt-get install -y varnish varnish-modules Install basic VMODs If you install Varnish 5.0.0 via Debian repo

Slide 227

Slide 227 text

vmod_cookie

Slide 228

Slide 228 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 229

Slide 229 text

vcl 4.0; import cookie; 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; } } } With vmod_cookie

Slide 230

Slide 230 text

vcl 4.0; import cookie; sub vcl_recv { cookie.parse(req.http.cookie); if(cookie.isset("PHPSESSID")) { return(synth(200,"PHPSESSID is set and contains the following value: " + cookie.get("PHPSESSID") + "")); } } Other vmod_cookie examples

Slide 231

Slide 231 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(); } Other vmod_cookie examples

Slide 232

Slide 232 text

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

Slide 233

Slide 233 text

vmod_accept

Slide 234

Slide 234 text

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

Slide 235

Slide 235 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 236

Slide 236 text

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

Slide 237

Slide 237 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"); encoding.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 238

Slide 238 text

Why is this useful?

Slide 239

Slide 239 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 240

Slide 240 text

More VMODs coming up later

Slide 241

Slide 241 text

In an ideal world

Slide 242

Slide 242 text

HTTP best practices > custom VCL

Slide 243

Slide 243 text

Reality sucks

Slide 244

Slide 244 text

No content

Slide 245

Slide 245 text

No content

Slide 246

Slide 246 text

Don’t trust the end-user

Slide 247

Slide 247 text

Cache-control ?

Slide 248

Slide 248 text

Legacy

Slide 249

Slide 249 text

Write VCL

Slide 250

Slide 250 text

Normalize

Slide 251

Slide 251 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 252

Slide 252 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 253

Slide 253 text

Static assets

Slide 254

Slide 254 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 255

Slide 255 text

Do you really want to cache static assets?

Slide 256

Slide 256 text

Nginx or Apache can be fast enough for that

Slide 257

Slide 257 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 258

Slide 258 text

URL whitelist/blacklist

Slide 259

Slide 259 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 260

Slide 260 text

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

Slide 261

Slide 261 text

Those damn cookies again!

Slide 262

Slide 262 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 263

Slide 263 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 264

Slide 264 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 265

Slide 265 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 266

Slide 266 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 267

Slide 267 text

Alternative language cache variation

Slide 268

Slide 268 text

sub vcl_hash { hash_data(req.http.Accept-Language); } Language cookie cache variation Or just send a “Vary:Accept-Language” header

Slide 269

Slide 269 text

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

Slide 270

Slide 270 text

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

Slide 271

Slide 271 text

Block caching

Slide 272

Slide 272 text

No content

Slide 273

Slide 273 text

Code renders single HTTP response

Slide 274

Slide 274 text

Lowest denominator: no cache

Slide 275

Slide 275 text

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

Slide 276

Slide 276 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 277

Slide 277 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 278

Slide 278 text

ESI_xmlerror No ESI processing, first char not '<'. (See feature esi_disable_xml_check) Expects HTML/ XML tags

Slide 279

Slide 279 text

-p feature=+esi_disable_xml_check Add as startup option

Slide 280

Slide 280 text

req_top Get information about parent request in an ESI call

Slide 281

Slide 281 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 282

Slide 282 text

ESI vs AJAX

Slide 283

Slide 283 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 284

Slide 284 text

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

Slide 285

Slide 285 text

Choose wisely

Slide 286

Slide 286 text

Assemble at the view layer

Slide 287

Slide 287 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 288

Slide 288 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 289

Slide 289 text

ESI vs HInclude Extra parameters

Slide 290

Slide 290 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>

Slide 291

Slide 291 text

Control Time To Live

Slide 292

Slide 292 text

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

Slide 293

Slide 293 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 294

Slide 294 text

Debugging

Slide 295

Slide 295 text

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

Slide 296

Slide 296 text

Anonymize

Slide 297

Slide 297 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 298

Slide 298 text

Breaking news isn't breaking

Slide 299

Slide 299 text

Purging

Slide 300

Slide 300 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 301

Slide 301 text

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

Slide 302

Slide 302 text

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

Slide 303

Slide 303 text

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

Slide 304

Slide 304 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 305

Slide 305 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 306

Slide 306 text

Lurker-friendly bans

Slide 307

Slide 307 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 308

Slide 308 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 309

Slide 309 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 310

Slide 310 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 311

Slide 311 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 312

Slide 312 text

Banning through Varnishadm

Slide 313

Slide 313 text

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

Slide 314

Slide 314 text

Banning through socket

Slide 315

Slide 315 text

Varnishadm is a binary on top of the socket

Slide 316

Slide 316 text

Varnishadm uses the secret file to authenticate automatically

Slide 317

Slide 317 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 318

Slide 318 text

Slide 319

Slide 319 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 320

Slide 320 text

Secure your access the the admin socket

Slide 321

Slide 321 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 322

Slide 322 text

Admin socket does more than banning

Slide 323

Slide 323 text

----------------------------- 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. help 200 613 auth backend.list [-p] [] backend.set_health [auto|healthy|sick] ban [&& ...] ban.list banner help [] panic.clear [-z] panic.show param.set param.show [-l] [] ping [] quit start status stop storage.list vcl.discard vcl.inline [auto|cold|warm] vcl.label vcl.list vcl.load [auto|cold|warm] vcl.show [-v] vcl.state [auto|cold|warm] vcl.use

Slide 324

Slide 324 text

Refresh content

Slide 325

Slide 325 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 326

Slide 326 text

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

Slide 327

Slide 327 text

Tag-base invalidation

Slide 328

Slide 328 text

vmod_xkey

Slide 329

Slide 329 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 330

Slide 330 text

'.PHP_EOL; Add “xkey” headers Register tags

Slide 331

Slide 331 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 332

Slide 332 text

Header-based invalidation

Slide 333

Slide 333 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 334

Slide 334 text

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

Slide 335

Slide 335 text

Grace mode

Slide 336

Slide 336 text

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

Slide 337

Slide 337 text

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

Slide 338

Slide 338 text

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

Slide 339

Slide 339 text

Let's write some VCL

Slide 340

Slide 340 text

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

Slide 341

Slide 341 text

Varnishstat Realtime statistics

Slide 342

Slide 342 text

No content

Slide 343

Slide 343 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 344

Slide 344 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 345

Slide 345 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 346

Slide 346 text

varnishstat -f MAIN.cache*

Slide 347

Slide 347 text

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

Slide 348

Slide 348 text

VSL

Slide 349

Slide 349 text

Varnish Shared memory Logging

Slide 350

Slide 350 text

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

Slide 351

Slide 351 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 352

Slide 352 text

Transactions

Slide 353

Slide 353 text

✓ Items of work ✓ Identified by VXID ✓ 2 kinds: ✓ Sessions ✓ Requests Transactions

Slide 354

Slide 354 text

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

Slide 355

Slide 355 text

No content

Slide 356

Slide 356 text

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

Slide 357

Slide 357 text

Example composition that will be monitored

Slide 358

Slide 358 text

varnishlog -i Begin,ReqUrl,Link,BereqURL

Slide 359

Slide 359 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 360

Slide 360 text

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

Slide 361

Slide 361 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 362

Slide 362 text

No content

Slide 363

Slide 363 text

Tags

Slide 364

Slide 364 text

Request tags

Slide 365

Slide 365 text

✓ ReqMethod ✓ ReqUrl ✓ ReqProtocol ✓ ReqHeader Request tags

Slide 366

Slide 366 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 367

Slide 367 text

Response tags

Slide 368

Slide 368 text

✓ RespProtocol ✓ RespStatus ✓ RespReason ✓ RespHeader Response tags

Slide 369

Slide 369 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 370

Slide 370 text

Backend tags

Slide 371

Slide 371 text

- BackendOpen 19 boot.default 127.0.0.1 8080 127.0.0.1 62552 - BackendClose 19 boot.default

Slide 372

Slide 372 text

Backend request tags

Slide 373

Slide 373 text

✓ BereqMethod ✓ BereqUrl ✓ BereqProtocol ✓ BereqHeader Backend request tags

Slide 374

Slide 374 text

Backend response tags

Slide 375

Slide 375 text

✓ BerespProtocol ✓ BerespStatus ✓ BerespReason ✓ BerespHeader Backens response tags

Slide 376

Slide 376 text

Object tags

Slide 377

Slide 377 text

✓ ObjProtocol ✓ ObjStatus ✓ ObjReason ✓ ObjHeader Object tags

Slide 378

Slide 378 text

VCL tags

Slide 379

Slide 379 text

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

Slide 380

Slide 380 text

* << Request >> 5 - ReqURL / - VCL_call RECV - VCL_return hash - VCL_call HASH - VCL_return lookup - VCL_call MISS - VCL_return fetch - VCL_call DELIVER - VCL_return deliver ** << BeReq >> 6 -- BereqURL / -- VCL_call BACKEND_FETCH -- VCL_return fetch -- VCL_call BACKEND_RESPONSE -- VCL_return deliver

Slide 381

Slide 381 text

* << Request >> 6 - ReqURL / - VCL_call RECV - ReqURL / - VCL_return hash - VCL_call HASH - VCL_return lookup - VCL_call HIT - VCL_return deliver - VCL_call DELIVER - VCL_return deliver

Slide 382

Slide 382 text

* << Request >> 32789 - ReqURL /header - ExpBan 98355 banned lookup ExpBan tag

Slide 383

Slide 383 text

* << Request >> 98369 - ReqURL /footer - Hit 65597 Hit tag varnishlog -i "ReqUrl,Hit"

Slide 384

Slide 384 text

** << Request >> 6 -- ReqURL /footer -- HitPass 3 HitPass tag

Slide 385

Slide 385 text

TTL tag

Slide 386

Slide 386 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 387

Slide 387 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 388

Slide 388 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 389

Slide 389 text

-- RFC 500 10 -1 1501598872 1501598869 1501598872 0 500 ✓ Don’t screw with the age header ✓ Only 497 second effective TTL ✓ Custom Age header (3 seconds off)

Slide 390

Slide 390 text

Begin tag

Slide 391

Slide 391 text

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

Slide 392

Slide 392 text

Link tag

Slide 393

Slide 393 text

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

Slide 394

Slide 394 text

Timestamp tag

Slide 395

Slide 395 text

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

Slide 396

Slide 396 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 397

Slide 397 text

Filtering output

Slide 398

Slide 398 text

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

Slide 399

Slide 399 text

Include tags

Slide 400

Slide 400 text

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

Slide 401

Slide 401 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 402

Slide 402 text

Exclude tags

Slide 403

Slide 403 text

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

Slide 404

Slide 404 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 405

Slide 405 text

Include tags by regex

Slide 406

Slide 406 text

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

Slide 407

Slide 407 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 408

Slide 408 text

Exclude tags by regex

Slide 409

Slide 409 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 410

Slide 410 text

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

Slide 411

Slide 411 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 412

Slide 412 text

All-in-one

Slide 413

Slide 413 text

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

Slide 414

Slide 414 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 415

Slide 415 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 416

Slide 416 text

VSL queries

Slide 417

Slide 417 text

Filtering fields from all transactions Filtering transactions

Slide 418

Slide 418 text

{level}taglist:record-prefix[field]

Slide 419

Slide 419 text

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

Slide 420

Slide 420 text

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

Slide 421

Slide 421 text

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

Slide 422

Slide 422 text

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

Slide 423

Slide 423 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 424

Slide 424 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 425

Slide 425 text

Other options

Slide 426

Slide 426 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 427

Slide 427 text

Varnishtop

Slide 428

Slide 428 text

Same syntax as varnishlog. Output is incremental

Slide 429

Slide 429 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 430

Slide 430 text

Common Varnishlog scenarios

Slide 431

Slide 431 text

Why didn’t Varnish serve this request from cache?

Slide 432

Slide 432 text

Request not cacheable

Slide 433

Slide 433 text

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

Slide 434

Slide 434 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 435

Slide 435 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 436

Slide 436 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 437

Slide 437 text

Response not cacheable

Slide 438

Slide 438 text

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

Slide 439

Slide 439 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 440

Slide 440 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 441

Slide 441 text

Let's look at some logs

Slide 442

Slide 442 text

Let's write a Symfony application

Slide 443

Slide 443 text

composer create-project symfony/skeleton:^4.0 demo cd demo composer req --dev webserver composer req twig composer req template composer req expression-language composer req translation composer req sensio/framework-extra-bundle

Slide 444

Slide 444 text

https://github.com/ThijsFeryn/ cacheable-sites-symfony4/tree/ start

Slide 445

Slide 445 text

Support dynamic language selection

Slide 446

Slide 446 text

getRequest(); $preferredLanguage = $request->getPreferredLanguage(); if(null !== $preferredLanguage) { $request->setLocale($preferredLanguage); } } } src/EvenListener/ LocaleListener.php

Slide 447

Slide 447 text

parameters: locale: 'en' services: _defaults: autowire: true autoconfigure: true public: false App\: resource: '../src/*' exclude: '../src/{Entity,Migrations,Tests,Kernel.php}' App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments'] App\EventListener\LocaleListener: tags: - { name: kernel.event_listener, event: kernel.request, priority: 100} config/ services.yml

Slide 448

Slide 448 text

Make it cacheable

Slide 449

Slide 449 text

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

Slide 450

Slide 450 text

composer require symfony-bundles/redis-bundle + Redis container

Slide 451

Slide 451 text

Authentication

Slide 452

Slide 452 text

Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l

Slide 453

Slide 453 text

PHPSESSID: laken37oumlkrndoo91k3trlv5

Slide 454

Slide 454 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 455

Slide 455 text

No content

Slide 456

Slide 456 text

Let's add authentication

Slide 457

Slide 457 text

composer require security

Slide 458

Slide 458 text

security: access_denied_url: /login encoders: Symfony\Component\Security\Core\User\User: algorithm: bcrypt cost: 12 providers: in_memory: memory: users: admin: password: $2y$12$R.XN53saKaGFZ5Zqqpv5h.9NzwP0RH4VlEGmRryW1G3cM3ov1yq32 roles: 'ROLE_ADMIN' firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: anonymous: true form_login: check_path: /login logout: path: /logout target: / access_control: - { path: ^/private, roles: ROLE_ADMIN } config/ packages/ security.yml

Slide 459

Slide 459 text

index: path: / controller: App\Controller\DefaultController::index logout: path: /logout config/routes.yml

Slide 460

Slide 460 text

/** * @Route("/private", name="private") */ public function private(Request $request) { $response = $this->render('private.twig') ->setPrivate(); $response->headers->addCacheControlDirective('no-store'); return $response; }

Slide 461

Slide 461 text

https://github.com/ThijsFeryn/ cacheable-sites-symfony4/tree/ auth

Slide 462

Slide 462 text

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

Slide 463

Slide 463 text

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

Slide 464

Slide 464 text

Push session information from the server to the client

Slide 465

Slide 465 text

JSON Web Tokens

Slide 466

Slide 466 text

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

Slide 467

Slide 467 text

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

Slide 468

Slide 468 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 469

Slide 469 text

https://jwt.io

Slide 470

Slide 470 text

No content

Slide 471

Slide 471 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 472

Slide 472 text

Application creates JWT alongside the regular session

Slide 473

Slide 473 text

Template loads stateful data from JWT in Javascript

Slide 474

Slide 474 text

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

Slide 475

Slide 475 text

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

Slide 476

Slide 476 text

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

Slide 477

Slide 477 text

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

Slide 478

Slide 478 text

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

Slide 479

Slide 479 text

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

Slide 480

Slide 480 text

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

Slide 481

Slide 481 text

Using vmod_digest for HMAC & base64

Slide 482

Slide 482 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 483

Slide 483 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 484

Slide 484 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 485

Slide 485 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 486

Slide 486 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 487

Slide 487 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 488

Slide 488 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 489

Slide 489 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 490

Slide 490 text

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

Slide 491

Slide 491 text

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

Slide 492

Slide 492 text

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

Slide 493

Slide 493 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 494

Slide 494 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 495

Slide 495 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 496

Slide 496 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 497

Slide 497 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 498

Slide 498 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 499

Slide 499 text

Cache variations

Slide 500

Slide 500 text

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

Slide 501

Slide 501 text

Let's add JWT authentication

Slide 502

Slide 502 text

composer require lexik/jwt-authentication-bundle composer require lcobucci/jwt

Slide 503

Slide 503 text

https://github.com/ThijsFeryn/ cacheable-sites-symfony4/tree/ master

Slide 504

Slide 504 text

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

Slide 505

Slide 505 text

No content