SlideShare uma empresa Scribd logo
1 de 111
Baixar para ler offline
Developing cacheable
PHP applications
Thijs Feryn
Slow websites suck
Hi, I’m Thijs
I’m
@ThijsFeryn
on Twitter
I’m an
Evangelist
At
Cache
Don’t
recompute
if the data
hasn’t
changed
Whatifwe
coulddesign
oursoftware
withHTTP
cachingin
mind?
✓Portability
✓Developer empowerment
✓Control
✓Consistent caching behavior
Caching state of mind
Reverse
caching
proxy
Normally
User Server
With ReCaPro *
User ReCaPro Server
* Reverse Caching Proxy
Content Delivery Network
HTTP caching mechanisms
Expires: Sat, 09 Sep 2017 14:30:00 GMT
Cache-control: public, max-age=3600,
s-maxage=86400
Cache-control: private, no-cache, no-store
In an ideal world
✓Stateless
✓Well-defined TTL
✓Cache / no-cache per resource
✓Cache variations
✓Conditional requests
✓HTTP fragments for non-
cacheable content
In an ideal world
Reality
sucks
Common
problems
Time To Live
Cache variations
Authentication
Legacy
Twig templates
✓Multi-lingual (Accept-Language)
✓Nav
✓Header
✓Footer
✓Main
✓Login page & private content
composer require symfony/flex
composer require annotations twig translation
<?php
namespace AppController;
use SymfonyComponentHttpFoundationRequest;
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SymfonyBundleFrameworkBundleControllerController;
class DefaultController extends Controller
{
/**
* @Route("/", name="home")
*/
public function index()
{
return $this->render('index.twig');
}
}
src/Controller/DefaultController.php
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html" xmlns:hx="http://purl.org/NET/hinclude">
<head>
<title>{% block title %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
</head>
<body>
<div class="container-fluid">
{{ include('header.twig') }}
<div class="row">
<div class="col-sm-3 col-lg-2">
{{ include('nav.twig') }}
</div>
<div class="col-sm-9 col-lg-10">
{% block content %}{% endblock %}
</div>
</div>
{{ include('footer.twig') }}
</div>
</body>
</html>
templates/base.twig
{% extends "base.twig" %}
{% block title %}Home{% endblock %}
{% block content %}
<div class="page-header">
<h1>{{ 'example' | trans }}</h1>
</div>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris consequat orci
eget libero sollicitudin, non ultrices turpis mollis. Aliquam sit amet tempus elit.
Ut viverra risus enim, ut venenatis justo accumsan nec. Praesent a dolor tellus.
Maecenas non mauris leo. Pellentesque lobortis turpis at dapibus laoreet. Mauris
rhoncus nulla et urna mollis, et lobortis magna ornare. Etiam et sapien consequat,
egestas felis sit amet, dignissim enim.</p>
<p>Quisque quis mollis justo, imperdiet fermentum velit. Aliquam nulla justo,
consectetur et diam non, luctus commodo metus. Vestibulum fermentum efficitur nulla
non luctus. Nunc lorem nunc, mollis id efficitur et, aliquet sit amet ante. Sed
ipsum turpis, vehicula eu semper eu, malesuada eget leo. Vestibulum venenatis dui
id pulvinar suscipit. Etiam nec massa pharetra justo pharetra dignissim quis non
magna. Integer id convallis lectus. Nam non ullamcorper metus. Ut vestibulum ex ut
massa posuere tincidunt. Vestibulum hendrerit neque id lorem rhoncus aliquam. Duis
a facilisis metus, a faucibus nulla.</p>
{% endblock %}
templates/index.twig
<nav class="navbar navbar-default navbar-fixed-side">
<ul class="nav">
<li><a href="{{ url('home') }}">Home</a></li>
<li><a href="{{ url('login') }}">{{ 'log_in' | trans }}</a></li>
<li><a href="{{ url('private') }}">Private</a></li>
</ul>
</nav>
templates/nav.twig
<footer>
<hr />
<small>Footer</small>
</footer>
templates/footer.twig
home: Home
welcome : Welcome to the site
rendered : Rendered at %date%
example : An example page
log_in : Log in
login : Login
log_out : Log out
username : Username
password : Password
private : Private
privatetext : Looks like some very private data
translations/messages.en.yml
home: Start
welcome : Welkom op de site
rendered : Samengesteld op %date%
example : Een voorbeeldpagina
log_in : Inloggen
login : Login
log_out : Uitloggen
username : Gebruikersnaam
password : Wachtwoord
private : Privé
privatetext : Deze tekst ziet er vrij privé uit
translations/messages.nl.yml
<?php
namespace AppEventListener;
use SymfonyComponentHttpKernelEventGetResponseEvent;
class LocaleListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$preferredLanguage = $request->getPreferredLanguage();
if(null !== $preferredLanguage) {
$request->setLocale($preferredLanguage);
}
}
}
src/EventListener/LocaleListener.php
services:
_defaults:
autowire: true
autoconfigure: true
public: false
App:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'
AppController:
resource: '../src/Controller'
tags: ['controller.service_arguments']
AppEventListenerLocaleListener:
tags:
- { name: kernel.event_listener, event: kernel.request, priority: 100}
config/services.yml
Accept-Language: en
Accept-Language: nl
VS
The mission
Maximum
Cacheability
Cache-control
Cache-Control: public, s-maxage=500
<?php
namespace AppController;
use SymfonyComponentHttpFoundationRequest;
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SymfonyBundleFrameworkBundleControllerController;
class DefaultController extends Controller
{
/**
* @Route("/", name="home")
*/
public function index()
{
return $this
->render('index.twig')
->setSharedMaxAge(500)
->setPublic();
}
}
Conditional
requests
Only fetch
payload that has
changed
HTTP/1.1 200 OK
Otherwise:
HTTP/1.1 304 Not Modified
Conditional requests
HTTP/1.1 200 OK
Host: localhost
Etag: 7c9d70604c6061da9bb9377d3f00eb27
Content-type: text/html; charset=UTF-8
Hello world output
GET / HTTP/1.1
Host: localhost
User-Agent: curl/7.48.0
Conditional requests
HTTP/1.0 304 Not Modified
Host: localhost
Etag: 7c9d70604c6061da9bb9377d3f00eb27
GET / HTTP/1.1
Host: localhost
User-Agent: curl/7.48.0
If-None-Match:
7c9d70604c6061da9bb9377d3f00eb27
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 / HTTP/1.1
Host: localhost
User-Agent: curl/7.48.0
Conditional requests
HTTP/1.0 304 Not Modified
Host: localhost
Last-Modified: Fri, 22 Jul 2016 10:11:16 GMT
GET / HTTP/1.1
Host: localhost
User-Agent: curl/7.48.0
If-Last-Modified: Fri, 22 Jul 2016 10:11:16
GMT
composer require symfony-bundles/redis-bundle
<?php
namespace AppEventListener;
use SymfonyBridgeMonologLogger;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpKernelEventGetResponseEvent;
use SymfonyComponentHttpKernelEventFilterResponseEvent;
use SymfonyBundlesRedisBundleRedisClient as RedisClient;
class ConditionalRequestListener
{
protected $redis;
protected $logger;
public function __construct(RedisClient $redis)
{
$this->redis = $redis;
}
protected function isModified(Request $request, $etag)
{
if ($etags = $request->getETags()) {
return in_array($etag, $etags) || in_array('*', $etags);
}
return true;
}
...
src/EventListener/ConditionalRequestListener.php
{
$this->redis = $redis;
$this->logger = $logger;
}
protected function isModified(Request $request, $etag)
{
if ($etags = $request->getETags()) {
return in_array($etag, $etags) || in_array('*', $etags);
}
return true;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$etag = $this->redis->get('etag:'.md5($request->getUri()));
if(!$this->isModified($request,$etag)) {
$event->setResponse(Response::create('Not Modified',Response::HTTP_NOT_MODIFIED));
}
}
public function onKernelResponse(FilterResponseEvent $event)
{
$response = $event->getResponse();
$request = $event->getRequest();
$etag = md5($response->getContent());
$response->setEtag($etag);
if($this->isModified($request,$etag)) {
$this->redis->set('etag:'.md5($request->getUri()),$etag);
}
}
}
src/EventListener/ConditionalRequestListener.php
Do not cache
Cache-Control: private, no-store
/**
* @Route("/private", name="private")
*/
public function private()
{
$response = $this
->render('private.twig')
->setPrivate();
$response->headers->addCacheControlDirective('no-store');
return $response;
}
Dot not
cache private
page
session
cookie
Nocache
Code
renders
singleHTTP
response
Lowest
common
denominator:
nocache
Blockcaching
<esi:include src="/header" />
Edge Side Includes
✓Placeholder
✓Parsed by Varnish
✓Output is a composition of blocks
✓State per block
✓TTL per block
Surrogate-Capability: key="ESI/1.0"
Surrogate-Control: content="ESI/1.0"
Varnish
Backend
<esi:include src="/header" />
Parse ESI placeholdersVarnish
ESI
vs
AJAX
✓ 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
✓ Client-side
✓ Common knowledge
✓ Parallel processing
✓ Graceful
degradation
AJAX
- Processed by the
browser
- Extra roundtrips
- Somewhat slower
Subrequests
<div class="container-fluid">

{{ include('header.twig') }}

<div class="row">

<div class="col-sm-3 col-lg-2">

{{ include('nav.twig') }}

</div>

<div class="col-sm-9 col-lg-10">

{% block content %}{% endblock %}

</div>

</div>

{{ include('footer.twig') }}

</div>
<div class="container-fluid">

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

<div class="row">

<div class="col-sm-3 col-lg-2">

{{ render_esi(url('nav')) }}

</div>

<div class="col-sm-9 col-lg-10">

{% block content %}{% endblock %}

</div>

</div>

{{ render_esi(url('footer')) }}

</div>
<div class="container-fluid">
<esi:include src="/header" />
<div class="row">
<div class="col-sm-3 col-lg-2">
<esi:include src="/nav" />
</div>
<div class="col-sm-9 col-lg-10">
<div class="page-header">
<h1>An example page <small>Rendered at 2017-05-17 16:57:14</small></h1>
</div>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris consequat orci eget libero
sollicitudin,…</p>
</div>
</div>
<esi:include src="/footer" />
</div>
/**
* @Route("/", name="home")
*/
public function index()
{
return $this
->render('index.twig')
->setPublic()
->setSharedMaxAge(500);
}
/**
* @Route("/header", name="header")
*/
public function header()
{
$response = $this
->render('header.twig')
->setPrivate();
$response->headers->addCacheControlDirective('no-store');
return $response;
}
/**
* @Route("/footer", name="footer")
*/
public function footer()
{
$response = $this->render('footer.twig');
$response
->setSharedMaxAge(500)
->setPublic();
return $response;
}
/**
* @Route("/nav", name="nav")
*/
public function nav()
{
$response = $this->render('nav.twig');
$response
->setVary('X-Login',false)
->setSharedMaxAge(500)
->setPublic();
return $response;
}
Controller
action per
fragment
Problem:
no language
cache
variation
Vary: Accept-Language
<?php
namespace AppEventListener;
use SymfonyComponentHttpKernelEventFilterResponseEvent;
class VaryListener
{
public function onKernelResponse(FilterResponseEvent $event)
{
$response = $event->getResponse();
$response->setVary('Accept-Language',false);
}
}
src/EventListener/VaryListener.php
✓Navigation page
✓Private page
Weak spots
Not cached
because of
stateful content
Move state client-side
Replace PHP session with
JSON Web Tokens
JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pb
iIsImV4cCI6MTQ5NTUyODc1NiwibG9naW4iOnRydWV9.u4Idy-
SYnrFdnH1h9_sNc4OasORBJcrh2fPo1EOTre8
✓3 parts
✓Dot separated
✓Base64 encoded JSON
✓Header
✓Payload
✓Signature (HMAC with secret)
eyJzdWIiOiJhZG1pbiIsIm
V4cCI6MTQ5NTUyODc1Niwi
bG9naW4iOnRydWV9
{
"alg": "HS256",
 "typ": "JWT"
}
{
"sub": "admin",
"exp": 1495528756,
"login": true
}
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
eyJhbGciOiJIUzI1NiIsI
nR5cCI6IkpXVCJ9
u4Idy-
SYnrFdnH1h9_sNc4OasOR
BJcrh2fPo1EOTre8
JWT
Cookie:token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJz
dWIiOiJhZG1pbiIsImV4cCI6MTQ5NTUyODc1NiwibG9naW4iOnRydW
V9.u4Idy-SYnrFdnH1h9_sNc4OasORBJcrh2fPo1EOTre8
✓Stored in a cookie
✓Can be validated by Varnish
✓Payload can be processed by any
language (e.g. Javascript)
sub jwt {
std.log("Ready to perform some JWT magic");
if(cookie.isset("jwt_cookie")) {
#Extract header data from JWT
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"));
#Don't allow invalid JWT header
if(var.get("type") == "JWT" && var.get("algorithm") == "HS256") {
#Extract signature & payload data from JWT
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("jti",regsub(var.get("payload"),{"^.*?"jti"s*:s*"([a-z0-9A-Z_-]+)".*?$"},"1"));
var.set("userId",regsub(var.get("payload"),{"^.*?"uid"s*:s*"([0-9]+)".*?$"},"1"));
var.set("roles",regsub(var.get("payload"),{"^.*?"roles"s*:s*"([a-z0-9A-Z_-, ]+)".*?$"},"1"));
#Only allow valid userId
if(var.get("userId") ~ "^d+$") {
#Don't allow expired JWT
if(std.time(var.get("exp"),now) >= now) {
#SessionId should match JTI value from JWT
if(cookie.get(var.get("sessionCookie")) == var.get("jti")) {
#Don't allow invalid JWT signature
if(var.get("signature") == var.get("currentSignature")) {
#The sweet spot
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: session cookie doesn't match JTI." + var.get("sessionCookie") + ": " + cookie.get(var.get("sessionCookie")) + ", JTI:" + var.get("jti"));
}
} 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);
}
#Look for full private content
if(req.url ~ "/node/2" && req.url !~ "^/user/login") {
if(req.http.X-login != "true") {
return(synth(302,"/user/login?destination=" + req.url));
}
}
}
Insert incomprehensible
Varnish VCL code here …
X-Login: true
End result:
X-Login: false
Custom request
header set by
Varnish
Extra cache
variation
required
Vary: Accept-Language, X-Login
Content for logged-in
& anonymous differs
<script language="JavaScript">

function getCookie(name) {

var value = "; " + document.cookie;

var parts = value.split("; " + name + "=");

if (parts.length == 2) return parts.pop().split(";").shift();

}

function parseJwt (token) {

var base64Url = token.split('.')[1];

var base64 = base64Url.replace('-', '+').replace('_', '/');

return JSON.parse(window.atob(base64));

};

$(document).ready(function(){

if ($.cookie('token') != null ){

var token = parseJwt($.cookie("token"));

$("#usernameLabel").html(', ' + token.sub);

}

});

</script>
Parse JWT
in Javascript
Does not require
backend access
composer require firebase/php-jwt
<?php
namespace AppService;
use FirebaseJWTJWT;
use SymfonyComponentHttpFoundationCookie;
class JwtAuthentication
{
protected $key;
protected $username;
protected $password;
public function __construct($key,$username,$password)
{
$this->key = $key;
$this->username = $username;
$this->password = $password;
}
public function jwt($username)
{
return JWT::encode([
'sub'=>$username,
'exp'=>time() + (4 * 24 * 60 * 60),
'login'=>true,
],$this->key);
}
public function createCookie($username)
{
return new Cookie("token",$this->jwt($username), time() + (3600 * 48), '/', null,
false, false);
}
public function validate($token)
{
src/Service/JwtAuthentication.php
$this->password = $password;
}
public function jwt($username)
{
return JWT::encode([
'sub'=>$username,
'exp'=>time() + (4 * 24 * 60 * 60),
//'exp'=>time() + 60,
'login'=>true,
],$this->key);
}
public function createCookie($username)
{
return new Cookie("token",$this->jwt($username), time() + (3600 * 48), '/', null,
false, false);
}
public function validate($token)
{
try {
$data = JWT::decode($token,$this->key,['HS256']);
$data = (array)$data;
if($data['sub'] !== $this->username) {
return false;
}
return true;
} catch(UnexpectedValueException $e) {
return false;
}
}
}
src/Service/JwtAuthentication.php
services:
AppServiceJwtAuthentication:
arguments:
$key: '%env(JWT_KEY)%'
$username: '%env(JWT_USERNAME)%'
$password: '%env(JWT_PASSWORD)%'
src/Service/JwtAuthentication.php
###> JWT authentication ###
JWT_KEY=SlowWebSitesSuck
JWT_USERNAME=admin
JWT_PASSWORD=$2y$10$431rvq1qS9ewNFP0Gti/o.kBbuMK4zs8IDTLlxm5uzV7cbv8wKt0K
###< JWT authentication ###
.env
/**
* @Route("/login", name="login", methods="GET")
*/
public function login(Request $request, JwtAuthentication $jwt)
{
if($jwt->validate($request->cookies->get('token'))) {
return new RedirectResponse($this->generateUrl('home'));
}
$response = $this->render('login.twig',['loginLogoutUrl'=>$this-
>generateUrl('login'),'loginLogoutLabel'=>'log_in']);
$response
->setSharedMaxAge(500)
->setVary('X-Login',false)
->setPublic();
return $response;
}
/**
* @Route("/login", name="loginpost", methods="POST")
*/
public function loginpost(Request $request, JwtAuthentication $jwt)
{
$username = $request->get('username');
$password = $request->get('password');
if(!$username || !$password || getenv('JWT_USERNAME') != $username || !
password_verify($password,getenv('JWT_PASSWORD'))) {
return new RedirectResponse($this->generateUrl('login'));
}
$response = new RedirectResponse($this->generateUrl('home'));
$response->headers->setCookie($jwt->createCookie($username));
return $response;
}
src/Controller/DefaultController.php
/**
* @Route("/logout", name="logout")
*/
public function logout()
{
$response = new RedirectResponse($this->generateUrl('login'));
$response->headers->clearCookie('token');
return $response;
}
src/Controller/DefaultController.php
/**
* @Route("/nav", name="nav")
*/
public function nav(Request $request, JwtAuthentication $jwt)
{
$response = $this->render('nav.twig', [
'validJwt' => $jwt->validate(
$request->cookies->get('token')
)])
->setVary('X-Login',false)
->setMaxAge(100)
->setSharedMaxAge(500)
->setPublic();
return $response;
}
src/Controller/DefaultController.php
<nav class="navbar navbar-default navbar-fixed-side">
<ul class="nav">
<li><a href="{{ url('home') }}">Home</a></li>
{% if validJwt == true %}
<li><a href="{{ url('logout') }}">{{ 'log_out' | trans }}</a></li>
{% else %}
<li><a href="{{ url('login') }}">{{ 'log_in' | trans }}</a></li>
{% endif %}
<li><a href="{{ url('private') }}">Private</a></li>
</ul>
</nav>
templates/nav.twig
{% extends "base.twig" %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="page-header">
<h1>{{ 'log_in' | trans }}</h1>
</div>
<form class="form-horizontal" method="post" action="{{ url('loginpost') }}">
<div class="form-group">
<label for="usernameInput" class="col-sm-2 control-label">{{ 'username' | trans }}</label>
<div class="col-sm-6">
<input type="text" name="username" class="form-control" id="usernameInput"
placeholder="{{ 'username' | trans }}">
</div>
</div>
<div class="form-group">
<label for="passwordInput" class="col-sm-2 control-label">{{ 'password' | trans }}</label>
<div class="col-sm-6">
<input type="password" name="password" class="form-control" id="passwordInput"
placeholder="{{ 'password' | trans }}">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">{{ 'log_in' | trans }}</button>
</div>
</div>
</form>
{% endblock %}
templates/login.twig
Cookie:
token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1Ni
J9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTUyMDk0M
jExNSwibG9naW4iOnRydWV9._161Lf8BKkbzjqVv
5d62O5aMdCotKvCqd7F8qFqZC2Y
With the proper VCL, Varnish
can process the JWT and
make cache variations for
authenticated & anonymous
content
https://github.com/ThijsFeryn/
cacheable-sites-symfony4
https://feryn.eu
https://twitter.com/ThijsFeryn
https://instagram.com/ThijsFeryn
Developing cacheable PHP applications for maximum performance

Mais conteúdo relacionado

Mais procurados

Advanced HTTP Caching
Advanced HTTP CachingAdvanced HTTP Caching
Advanced HTTP CachingMartin Breest
 
Care and feeding notes
Care and feeding notesCare and feeding notes
Care and feeding notesPerrin Harkins
 
ApacheConNA 2015: What's new in Apache httpd 2.4
ApacheConNA 2015: What's new in Apache httpd 2.4ApacheConNA 2015: What's new in Apache httpd 2.4
ApacheConNA 2015: What's new in Apache httpd 2.4Jim Jagielski
 
PyGotham 2014 Introduction to Profiling
PyGotham 2014 Introduction to ProfilingPyGotham 2014 Introduction to Profiling
PyGotham 2014 Introduction to ProfilingPerrin Harkins
 
LCA2014 - Introduction to Go
LCA2014 - Introduction to GoLCA2014 - Introduction to Go
LCA2014 - Introduction to Godreamwidth
 
Devoxx Maroc 2015 HTTP 1, HTTP 2 and folks
Devoxx Maroc  2015 HTTP 1, HTTP 2 and folksDevoxx Maroc  2015 HTTP 1, HTTP 2 and folks
Devoxx Maroc 2015 HTTP 1, HTTP 2 and folksNicolas Martignole
 
HTTP caching with Varnish
HTTP caching with VarnishHTTP caching with Varnish
HTTP caching with VarnishDavid de Boer
 
Caching the uncacheable with Varnish - DevDays 2021
Caching the uncacheable with Varnish - DevDays 2021Caching the uncacheable with Varnish - DevDays 2021
Caching the uncacheable with Varnish - DevDays 2021Thijs Feryn
 
Interactive web. O rly?
Interactive web. O rly?Interactive web. O rly?
Interactive web. O rly?timbc
 
ApacheCon 2014 - What's New in Apache httpd 2.4
ApacheCon 2014 - What's New in Apache httpd 2.4ApacheCon 2014 - What's New in Apache httpd 2.4
ApacheCon 2014 - What's New in Apache httpd 2.4Jim Jagielski
 
Altitude San Francisco 2018: Programming the Edge
Altitude San Francisco 2018: Programming the EdgeAltitude San Francisco 2018: Programming the Edge
Altitude San Francisco 2018: Programming the EdgeFastly
 
Northeast PHP - High Performance PHP
Northeast PHP - High Performance PHPNortheast PHP - High Performance PHP
Northeast PHP - High Performance PHPJonathan Klein
 
HipHop VM: overclocking Symfony
HipHop VM: overclocking SymfonyHipHop VM: overclocking Symfony
HipHop VM: overclocking SymfonyVadim Borodavko
 
Going crazy with Varnish and Symfony
Going crazy with Varnish and SymfonyGoing crazy with Varnish and Symfony
Going crazy with Varnish and SymfonyDavid de Boer
 
Genkidama:実装と課題
Genkidama:実装と課題Genkidama:実装と課題
Genkidama:実装と課題Takuya ASADA
 
Site Performance Optimization for Joomla #jwc13
Site Performance Optimization for Joomla #jwc13Site Performance Optimization for Joomla #jwc13
Site Performance Optimization for Joomla #jwc13Hans Kuijpers
 
Apache and PHP: Why httpd.conf is your new BFF!
Apache and PHP: Why httpd.conf is your new BFF!Apache and PHP: Why httpd.conf is your new BFF!
Apache and PHP: Why httpd.conf is your new BFF!Jeff Jones
 
Single page apps with drupal 7
Single page apps with drupal 7Single page apps with drupal 7
Single page apps with drupal 7Chris Tankersley
 
From zero to hero - Easy log centralization with Logstash and Elasticsearch
From zero to hero - Easy log centralization with Logstash and ElasticsearchFrom zero to hero - Easy log centralization with Logstash and Elasticsearch
From zero to hero - Easy log centralization with Logstash and ElasticsearchRafał Kuć
 

Mais procurados (20)

Advanced HTTP Caching
Advanced HTTP CachingAdvanced HTTP Caching
Advanced HTTP Caching
 
Care and feeding notes
Care and feeding notesCare and feeding notes
Care and feeding notes
 
ApacheConNA 2015: What's new in Apache httpd 2.4
ApacheConNA 2015: What's new in Apache httpd 2.4ApacheConNA 2015: What's new in Apache httpd 2.4
ApacheConNA 2015: What's new in Apache httpd 2.4
 
PyGotham 2014 Introduction to Profiling
PyGotham 2014 Introduction to ProfilingPyGotham 2014 Introduction to Profiling
PyGotham 2014 Introduction to Profiling
 
LCA2014 - Introduction to Go
LCA2014 - Introduction to GoLCA2014 - Introduction to Go
LCA2014 - Introduction to Go
 
Devoxx Maroc 2015 HTTP 1, HTTP 2 and folks
Devoxx Maroc  2015 HTTP 1, HTTP 2 and folksDevoxx Maroc  2015 HTTP 1, HTTP 2 and folks
Devoxx Maroc 2015 HTTP 1, HTTP 2 and folks
 
HTTP caching with Varnish
HTTP caching with VarnishHTTP caching with Varnish
HTTP caching with Varnish
 
Caching the uncacheable with Varnish - DevDays 2021
Caching the uncacheable with Varnish - DevDays 2021Caching the uncacheable with Varnish - DevDays 2021
Caching the uncacheable with Varnish - DevDays 2021
 
Web::Scraper
Web::ScraperWeb::Scraper
Web::Scraper
 
Interactive web. O rly?
Interactive web. O rly?Interactive web. O rly?
Interactive web. O rly?
 
ApacheCon 2014 - What's New in Apache httpd 2.4
ApacheCon 2014 - What's New in Apache httpd 2.4ApacheCon 2014 - What's New in Apache httpd 2.4
ApacheCon 2014 - What's New in Apache httpd 2.4
 
Altitude San Francisco 2018: Programming the Edge
Altitude San Francisco 2018: Programming the EdgeAltitude San Francisco 2018: Programming the Edge
Altitude San Francisco 2018: Programming the Edge
 
Northeast PHP - High Performance PHP
Northeast PHP - High Performance PHPNortheast PHP - High Performance PHP
Northeast PHP - High Performance PHP
 
HipHop VM: overclocking Symfony
HipHop VM: overclocking SymfonyHipHop VM: overclocking Symfony
HipHop VM: overclocking Symfony
 
Going crazy with Varnish and Symfony
Going crazy with Varnish and SymfonyGoing crazy with Varnish and Symfony
Going crazy with Varnish and Symfony
 
Genkidama:実装と課題
Genkidama:実装と課題Genkidama:実装と課題
Genkidama:実装と課題
 
Site Performance Optimization for Joomla #jwc13
Site Performance Optimization for Joomla #jwc13Site Performance Optimization for Joomla #jwc13
Site Performance Optimization for Joomla #jwc13
 
Apache and PHP: Why httpd.conf is your new BFF!
Apache and PHP: Why httpd.conf is your new BFF!Apache and PHP: Why httpd.conf is your new BFF!
Apache and PHP: Why httpd.conf is your new BFF!
 
Single page apps with drupal 7
Single page apps with drupal 7Single page apps with drupal 7
Single page apps with drupal 7
 
From zero to hero - Easy log centralization with Logstash and Elasticsearch
From zero to hero - Easy log centralization with Logstash and ElasticsearchFrom zero to hero - Easy log centralization with Logstash and Elasticsearch
From zero to hero - Easy log centralization with Logstash and Elasticsearch
 

Semelhante a Developing cacheable PHP applications for maximum performance

Less and faster – Cache tips for WordPress developers
Less and faster – Cache tips for WordPress developersLess and faster – Cache tips for WordPress developers
Less and faster – Cache tips for WordPress developersSeravo
 
Caching the Uncacheable
Caching the UncacheableCaching the Uncacheable
Caching the Uncacheabledanrot
 
What's New and Newer in Apache httpd-24
What's New and Newer in Apache httpd-24What's New and Newer in Apache httpd-24
What's New and Newer in Apache httpd-24Jim Jagielski
 
Drupal Performance - SerBenfiquista.com Case Study
Drupal Performance - SerBenfiquista.com Case StudyDrupal Performance - SerBenfiquista.com Case Study
Drupal Performance - SerBenfiquista.com Case Studyhernanibf
 
Caching and tuning fun for high scalability
Caching and tuning fun for high scalabilityCaching and tuning fun for high scalability
Caching and tuning fun for high scalabilityWim Godden
 
Altitude SF 2017: Optimizing your hit rate
Altitude SF 2017: Optimizing your hit rateAltitude SF 2017: Optimizing your hit rate
Altitude SF 2017: Optimizing your hit rateFastly
 
WordPress At Scale. WordCamp Dhaka 2019
WordPress At Scale. WordCamp Dhaka 2019WordPress At Scale. WordCamp Dhaka 2019
WordPress At Scale. WordCamp Dhaka 2019Anam Ahmed
 
MNPHP Scalable Architecture 101 - Feb 3 2011
MNPHP Scalable Architecture 101 - Feb 3 2011MNPHP Scalable Architecture 101 - Feb 3 2011
MNPHP Scalable Architecture 101 - Feb 3 2011Mike Willbanks
 
VUG5: Varnish at Opera Software
VUG5: Varnish at Opera SoftwareVUG5: Varnish at Opera Software
VUG5: Varnish at Opera SoftwareCosimo Streppone
 
Choosing A Proxy Server - Apachecon 2014
Choosing A Proxy Server - Apachecon 2014Choosing A Proxy Server - Apachecon 2014
Choosing A Proxy Server - Apachecon 2014bryan_call
 
REST in ( a mobile ) peace @ WHYMCA 05-21-2011
REST in ( a mobile ) peace @ WHYMCA 05-21-2011REST in ( a mobile ) peace @ WHYMCA 05-21-2011
REST in ( a mobile ) peace @ WHYMCA 05-21-2011Alessandro Nadalin
 
20190516 web security-basic
20190516 web security-basic20190516 web security-basic
20190516 web security-basicMksYi
 
Caching with Varnish
Caching with VarnishCaching with Varnish
Caching with Varnishschoefmax
 
Profiling PHP with Xdebug / Webgrind
Profiling PHP with Xdebug / WebgrindProfiling PHP with Xdebug / Webgrind
Profiling PHP with Xdebug / WebgrindSam Keen
 
Optimizing CakePHP 2.x Apps
Optimizing CakePHP 2.x AppsOptimizing CakePHP 2.x Apps
Optimizing CakePHP 2.x AppsJuan Basso
 
Client Side Performance @ Xero
Client Side Performance @ XeroClient Side Performance @ Xero
Client Side Performance @ XeroCraig Walker
 
Ajax to the Moon
Ajax to the MoonAjax to the Moon
Ajax to the Moondavejohnson
 
Spreadshirt Techcamp 2018 - Hold until Told
Spreadshirt Techcamp 2018 - Hold until ToldSpreadshirt Techcamp 2018 - Hold until Told
Spreadshirt Techcamp 2018 - Hold until ToldMartin Breest
 
HTTP cache @ PUG Rome 03-29-2011
HTTP cache @ PUG Rome 03-29-2011HTTP cache @ PUG Rome 03-29-2011
HTTP cache @ PUG Rome 03-29-2011Alessandro Nadalin
 

Semelhante a Developing cacheable PHP applications for maximum performance (20)

Less and faster – Cache tips for WordPress developers
Less and faster – Cache tips for WordPress developersLess and faster – Cache tips for WordPress developers
Less and faster – Cache tips for WordPress developers
 
Caching the Uncacheable
Caching the UncacheableCaching the Uncacheable
Caching the Uncacheable
 
What's New and Newer in Apache httpd-24
What's New and Newer in Apache httpd-24What's New and Newer in Apache httpd-24
What's New and Newer in Apache httpd-24
 
Drupal Performance - SerBenfiquista.com Case Study
Drupal Performance - SerBenfiquista.com Case StudyDrupal Performance - SerBenfiquista.com Case Study
Drupal Performance - SerBenfiquista.com Case Study
 
Caching and tuning fun for high scalability
Caching and tuning fun for high scalabilityCaching and tuning fun for high scalability
Caching and tuning fun for high scalability
 
Altitude SF 2017: Optimizing your hit rate
Altitude SF 2017: Optimizing your hit rateAltitude SF 2017: Optimizing your hit rate
Altitude SF 2017: Optimizing your hit rate
 
WordPress At Scale. WordCamp Dhaka 2019
WordPress At Scale. WordCamp Dhaka 2019WordPress At Scale. WordCamp Dhaka 2019
WordPress At Scale. WordCamp Dhaka 2019
 
MNPHP Scalable Architecture 101 - Feb 3 2011
MNPHP Scalable Architecture 101 - Feb 3 2011MNPHP Scalable Architecture 101 - Feb 3 2011
MNPHP Scalable Architecture 101 - Feb 3 2011
 
VUG5: Varnish at Opera Software
VUG5: Varnish at Opera SoftwareVUG5: Varnish at Opera Software
VUG5: Varnish at Opera Software
 
Choosing A Proxy Server - Apachecon 2014
Choosing A Proxy Server - Apachecon 2014Choosing A Proxy Server - Apachecon 2014
Choosing A Proxy Server - Apachecon 2014
 
REST in ( a mobile ) peace @ WHYMCA 05-21-2011
REST in ( a mobile ) peace @ WHYMCA 05-21-2011REST in ( a mobile ) peace @ WHYMCA 05-21-2011
REST in ( a mobile ) peace @ WHYMCA 05-21-2011
 
20190516 web security-basic
20190516 web security-basic20190516 web security-basic
20190516 web security-basic
 
Caching with Varnish
Caching with VarnishCaching with Varnish
Caching with Varnish
 
Profiling PHP with Xdebug / Webgrind
Profiling PHP with Xdebug / WebgrindProfiling PHP with Xdebug / Webgrind
Profiling PHP with Xdebug / Webgrind
 
Optimizing CakePHP 2.x Apps
Optimizing CakePHP 2.x AppsOptimizing CakePHP 2.x Apps
Optimizing CakePHP 2.x Apps
 
Client Side Performance @ Xero
Client Side Performance @ XeroClient Side Performance @ Xero
Client Side Performance @ Xero
 
Ajax to the Moon
Ajax to the MoonAjax to the Moon
Ajax to the Moon
 
Into The Box 2018 Ortus Keynote
Into The Box 2018 Ortus KeynoteInto The Box 2018 Ortus Keynote
Into The Box 2018 Ortus Keynote
 
Spreadshirt Techcamp 2018 - Hold until Told
Spreadshirt Techcamp 2018 - Hold until ToldSpreadshirt Techcamp 2018 - Hold until Told
Spreadshirt Techcamp 2018 - Hold until Told
 
HTTP cache @ PUG Rome 03-29-2011
HTTP cache @ PUG Rome 03-29-2011HTTP cache @ PUG Rome 03-29-2011
HTTP cache @ PUG Rome 03-29-2011
 

Mais de Thijs Feryn

10 things that helped me advance my career - PHP UK Conference 2024
10 things that helped me advance my career - PHP UK Conference 202410 things that helped me advance my career - PHP UK Conference 2024
10 things that helped me advance my career - PHP UK Conference 2024Thijs Feryn
 
Distributed load testing with K6 - NDC London 2024
Distributed load testing with K6 - NDC London 2024Distributed load testing with K6 - NDC London 2024
Distributed load testing with K6 - NDC London 2024Thijs Feryn
 
HTTP headers that make your website go faster - devs.gent November 2023
HTTP headers that make your website go faster - devs.gent November 2023HTTP headers that make your website go faster - devs.gent November 2023
HTTP headers that make your website go faster - devs.gent November 2023Thijs Feryn
 
Living on the edge - EBU Horizons 2023
Living on the edge - EBU Horizons 2023Living on the edge - EBU Horizons 2023
Living on the edge - EBU Horizons 2023Thijs Feryn
 
Distributed Load Testing with k6 - DevOps Barcelona
Distributed Load Testing with k6 - DevOps BarcelonaDistributed Load Testing with k6 - DevOps Barcelona
Distributed Load Testing with k6 - DevOps BarcelonaThijs Feryn
 
Core web vitals meten om je site sneller te maken - Combell Partner Day 2023
Core web vitals meten om je site sneller te maken - Combell Partner Day 2023Core web vitals meten om je site sneller te maken - Combell Partner Day 2023
Core web vitals meten om je site sneller te maken - Combell Partner Day 2023Thijs Feryn
 
HTTP headers that make your website go faster
HTTP headers that make your website go fasterHTTP headers that make your website go faster
HTTP headers that make your website go fasterThijs Feryn
 
HTTP headers that will make your website go faster
HTTP headers that will make your website go fasterHTTP headers that will make your website go faster
HTTP headers that will make your website go fasterThijs Feryn
 
Distributed load testing with k6
Distributed load testing with k6Distributed load testing with k6
Distributed load testing with k6Thijs Feryn
 
HTTP logging met Varnishlog - PHPWVL 2022
HTTP logging met Varnishlog - PHPWVL 2022HTTP logging met Varnishlog - PHPWVL 2022
HTTP logging met Varnishlog - PHPWVL 2022Thijs Feryn
 
Taking Laravel to the edge with HTTP caching and Varnish
Taking Laravel to the edge with HTTP caching and VarnishTaking Laravel to the edge with HTTP caching and Varnish
Taking Laravel to the edge with HTTP caching and VarnishThijs Feryn
 
Build your own CDN with Varnish - Confoo 2022
Build your own CDN with Varnish - Confoo 2022Build your own CDN with Varnish - Confoo 2022
Build your own CDN with Varnish - Confoo 2022Thijs Feryn
 
Developing cacheable backend applications - Appdevcon 2019
Developing cacheable backend applications - Appdevcon 2019Developing cacheable backend applications - Appdevcon 2019
Developing cacheable backend applications - Appdevcon 2019Thijs Feryn
 
How Cloud addresses the needs of todays internet - Korazon 2018
How Cloud addresses the needs of todays internet - Korazon 2018How Cloud addresses the needs of todays internet - Korazon 2018
How Cloud addresses the needs of todays internet - Korazon 2018Thijs Feryn
 

Mais de Thijs Feryn (14)

10 things that helped me advance my career - PHP UK Conference 2024
10 things that helped me advance my career - PHP UK Conference 202410 things that helped me advance my career - PHP UK Conference 2024
10 things that helped me advance my career - PHP UK Conference 2024
 
Distributed load testing with K6 - NDC London 2024
Distributed load testing with K6 - NDC London 2024Distributed load testing with K6 - NDC London 2024
Distributed load testing with K6 - NDC London 2024
 
HTTP headers that make your website go faster - devs.gent November 2023
HTTP headers that make your website go faster - devs.gent November 2023HTTP headers that make your website go faster - devs.gent November 2023
HTTP headers that make your website go faster - devs.gent November 2023
 
Living on the edge - EBU Horizons 2023
Living on the edge - EBU Horizons 2023Living on the edge - EBU Horizons 2023
Living on the edge - EBU Horizons 2023
 
Distributed Load Testing with k6 - DevOps Barcelona
Distributed Load Testing with k6 - DevOps BarcelonaDistributed Load Testing with k6 - DevOps Barcelona
Distributed Load Testing with k6 - DevOps Barcelona
 
Core web vitals meten om je site sneller te maken - Combell Partner Day 2023
Core web vitals meten om je site sneller te maken - Combell Partner Day 2023Core web vitals meten om je site sneller te maken - Combell Partner Day 2023
Core web vitals meten om je site sneller te maken - Combell Partner Day 2023
 
HTTP headers that make your website go faster
HTTP headers that make your website go fasterHTTP headers that make your website go faster
HTTP headers that make your website go faster
 
HTTP headers that will make your website go faster
HTTP headers that will make your website go fasterHTTP headers that will make your website go faster
HTTP headers that will make your website go faster
 
Distributed load testing with k6
Distributed load testing with k6Distributed load testing with k6
Distributed load testing with k6
 
HTTP logging met Varnishlog - PHPWVL 2022
HTTP logging met Varnishlog - PHPWVL 2022HTTP logging met Varnishlog - PHPWVL 2022
HTTP logging met Varnishlog - PHPWVL 2022
 
Taking Laravel to the edge with HTTP caching and Varnish
Taking Laravel to the edge with HTTP caching and VarnishTaking Laravel to the edge with HTTP caching and Varnish
Taking Laravel to the edge with HTTP caching and Varnish
 
Build your own CDN with Varnish - Confoo 2022
Build your own CDN with Varnish - Confoo 2022Build your own CDN with Varnish - Confoo 2022
Build your own CDN with Varnish - Confoo 2022
 
Developing cacheable backend applications - Appdevcon 2019
Developing cacheable backend applications - Appdevcon 2019Developing cacheable backend applications - Appdevcon 2019
Developing cacheable backend applications - Appdevcon 2019
 
How Cloud addresses the needs of todays internet - Korazon 2018
How Cloud addresses the needs of todays internet - Korazon 2018How Cloud addresses the needs of todays internet - Korazon 2018
How Cloud addresses the needs of todays internet - Korazon 2018
 

Último

Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piececharlottematthew16
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DayH2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DaySri Ambati
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 

Último (20)

Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piece
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DayH2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 

Developing cacheable PHP applications for maximum performance

  • 6.
  • 7.
  • 10.
  • 13.
  • 16. With ReCaPro * User ReCaPro Server * Reverse Caching Proxy
  • 18.
  • 19. HTTP caching mechanisms Expires: Sat, 09 Sep 2017 14:30:00 GMT Cache-control: public, max-age=3600, s-maxage=86400 Cache-control: private, no-cache, no-store
  • 20. In an ideal world
  • 21. ✓Stateless ✓Well-defined TTL ✓Cache / no-cache per resource ✓Cache variations ✓Conditional requests ✓HTTP fragments for non- cacheable content In an ideal world
  • 24.
  • 25.
  • 30.
  • 32.
  • 34. composer require symfony/flex composer require annotations twig translation
  • 35. <?php namespace AppController; use SymfonyComponentHttpFoundationRequest; use SensioBundleFrameworkExtraBundleConfigurationRoute; use SymfonyBundleFrameworkBundleControllerController; class DefaultController extends Controller { /** * @Route("/", name="home") */ public function index() { return $this->render('index.twig'); } } src/Controller/DefaultController.php
  • 36. <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/html" xmlns:hx="http://purl.org/NET/hinclude"> <head> <title>{% block title %}{% endblock %}</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <div class="container-fluid"> {{ include('header.twig') }} <div class="row"> <div class="col-sm-3 col-lg-2"> {{ include('nav.twig') }} </div> <div class="col-sm-9 col-lg-10"> {% block content %}{% endblock %} </div> </div> {{ include('footer.twig') }} </div> </body> </html> templates/base.twig
  • 37. {% extends "base.twig" %} {% block title %}Home{% endblock %} {% block content %} <div class="page-header"> <h1>{{ 'example' | trans }}</h1> </div> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris consequat orci eget libero sollicitudin, non ultrices turpis mollis. Aliquam sit amet tempus elit. Ut viverra risus enim, ut venenatis justo accumsan nec. Praesent a dolor tellus. Maecenas non mauris leo. Pellentesque lobortis turpis at dapibus laoreet. Mauris rhoncus nulla et urna mollis, et lobortis magna ornare. Etiam et sapien consequat, egestas felis sit amet, dignissim enim.</p> <p>Quisque quis mollis justo, imperdiet fermentum velit. Aliquam nulla justo, consectetur et diam non, luctus commodo metus. Vestibulum fermentum efficitur nulla non luctus. Nunc lorem nunc, mollis id efficitur et, aliquet sit amet ante. Sed ipsum turpis, vehicula eu semper eu, malesuada eget leo. Vestibulum venenatis dui id pulvinar suscipit. Etiam nec massa pharetra justo pharetra dignissim quis non magna. Integer id convallis lectus. Nam non ullamcorper metus. Ut vestibulum ex ut massa posuere tincidunt. Vestibulum hendrerit neque id lorem rhoncus aliquam. Duis a facilisis metus, a faucibus nulla.</p> {% endblock %} templates/index.twig
  • 38. <nav class="navbar navbar-default navbar-fixed-side"> <ul class="nav"> <li><a href="{{ url('home') }}">Home</a></li> <li><a href="{{ url('login') }}">{{ 'log_in' | trans }}</a></li> <li><a href="{{ url('private') }}">Private</a></li> </ul> </nav> templates/nav.twig
  • 40. home: Home welcome : Welcome to the site rendered : Rendered at %date% example : An example page log_in : Log in login : Login log_out : Log out username : Username password : Password private : Private privatetext : Looks like some very private data translations/messages.en.yml
  • 41. home: Start welcome : Welkom op de site rendered : Samengesteld op %date% example : Een voorbeeldpagina log_in : Inloggen login : Login log_out : Uitloggen username : Gebruikersnaam password : Wachtwoord private : Privé privatetext : Deze tekst ziet er vrij privé uit translations/messages.nl.yml
  • 42. <?php namespace AppEventListener; use SymfonyComponentHttpKernelEventGetResponseEvent; class LocaleListener { public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); $preferredLanguage = $request->getPreferredLanguage(); if(null !== $preferredLanguage) { $request->setLocale($preferredLanguage); } } } src/EventListener/LocaleListener.php
  • 43. services: _defaults: autowire: true autoconfigure: true public: false App: resource: '../src/*' exclude: '../src/{Entity,Migrations,Tests,Kernel.php}' AppController: resource: '../src/Controller' tags: ['controller.service_arguments'] AppEventListenerLocaleListener: tags: - { name: kernel.event_listener, event: kernel.request, priority: 100} config/services.yml
  • 45.
  • 46.
  • 50. <?php namespace AppController; use SymfonyComponentHttpFoundationRequest; use SensioBundleFrameworkExtraBundleConfigurationRoute; use SymfonyBundleFrameworkBundleControllerController; class DefaultController extends Controller { /** * @Route("/", name="home") */ public function index() { return $this ->render('index.twig') ->setSharedMaxAge(500) ->setPublic(); } }
  • 52. Only fetch payload that has changed
  • 55. Conditional requests HTTP/1.1 200 OK Host: localhost Etag: 7c9d70604c6061da9bb9377d3f00eb27 Content-type: text/html; charset=UTF-8 Hello world output GET / HTTP/1.1 Host: localhost User-Agent: curl/7.48.0
  • 56. Conditional requests HTTP/1.0 304 Not Modified Host: localhost Etag: 7c9d70604c6061da9bb9377d3f00eb27 GET / HTTP/1.1 Host: localhost User-Agent: curl/7.48.0 If-None-Match: 7c9d70604c6061da9bb9377d3f00eb27
  • 57. 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 / HTTP/1.1 Host: localhost User-Agent: curl/7.48.0
  • 58. Conditional requests HTTP/1.0 304 Not Modified Host: localhost Last-Modified: Fri, 22 Jul 2016 10:11:16 GMT GET / HTTP/1.1 Host: localhost User-Agent: curl/7.48.0 If-Last-Modified: Fri, 22 Jul 2016 10:11:16 GMT
  • 60. <?php namespace AppEventListener; use SymfonyBridgeMonologLogger; use SymfonyComponentHttpFoundationResponse; use SymfonyComponentHttpFoundationRequest; use SymfonyComponentHttpKernelEventGetResponseEvent; use SymfonyComponentHttpKernelEventFilterResponseEvent; use SymfonyBundlesRedisBundleRedisClient as RedisClient; class ConditionalRequestListener { protected $redis; protected $logger; public function __construct(RedisClient $redis) { $this->redis = $redis; } protected function isModified(Request $request, $etag) { if ($etags = $request->getETags()) { return in_array($etag, $etags) || in_array('*', $etags); } return true; } ... src/EventListener/ConditionalRequestListener.php
  • 61. { $this->redis = $redis; $this->logger = $logger; } protected function isModified(Request $request, $etag) { if ($etags = $request->getETags()) { return in_array($etag, $etags) || in_array('*', $etags); } return true; } public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); $etag = $this->redis->get('etag:'.md5($request->getUri())); if(!$this->isModified($request,$etag)) { $event->setResponse(Response::create('Not Modified',Response::HTTP_NOT_MODIFIED)); } } public function onKernelResponse(FilterResponseEvent $event) { $response = $event->getResponse(); $request = $event->getRequest(); $etag = md5($response->getContent()); $response->setEtag($etag); if($this->isModified($request,$etag)) { $this->redis->set('etag:'.md5($request->getUri()),$etag); } } } src/EventListener/ConditionalRequestListener.php
  • 64. /** * @Route("/private", name="private") */ public function private() { $response = $this ->render('private.twig') ->setPrivate(); $response->headers->addCacheControlDirective('no-store'); return $response; } Dot not cache private page
  • 66.
  • 70. <esi:include src="/header" /> Edge Side Includes ✓Placeholder ✓Parsed by Varnish ✓Output is a composition of blocks ✓State per block ✓TTL per block
  • 73. ✓ 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
  • 74. ✓ Client-side ✓ Common knowledge ✓ Parallel processing ✓ Graceful degradation AJAX - Processed by the browser - Extra roundtrips - Somewhat slower
  • 76. <div class="container-fluid">
 {{ include('header.twig') }}
 <div class="row">
 <div class="col-sm-3 col-lg-2">
 {{ include('nav.twig') }}
 </div>
 <div class="col-sm-9 col-lg-10">
 {% block content %}{% endblock %}
 </div>
 </div>
 {{ include('footer.twig') }}
 </div> <div class="container-fluid">
 {{ render_esi(url('header')) }}
 <div class="row">
 <div class="col-sm-3 col-lg-2">
 {{ render_esi(url('nav')) }}
 </div>
 <div class="col-sm-9 col-lg-10">
 {% block content %}{% endblock %}
 </div>
 </div>
 {{ render_esi(url('footer')) }}
 </div>
  • 77. <div class="container-fluid"> <esi:include src="/header" /> <div class="row"> <div class="col-sm-3 col-lg-2"> <esi:include src="/nav" /> </div> <div class="col-sm-9 col-lg-10"> <div class="page-header"> <h1>An example page <small>Rendered at 2017-05-17 16:57:14</small></h1> </div> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris consequat orci eget libero sollicitudin,…</p> </div> </div> <esi:include src="/footer" /> </div>
  • 78. /** * @Route("/", name="home") */ public function index() { return $this ->render('index.twig') ->setPublic() ->setSharedMaxAge(500); } /** * @Route("/header", name="header") */ public function header() { $response = $this ->render('header.twig') ->setPrivate(); $response->headers->addCacheControlDirective('no-store'); return $response; } /** * @Route("/footer", name="footer") */ public function footer() { $response = $this->render('footer.twig'); $response ->setSharedMaxAge(500) ->setPublic(); return $response; } /** * @Route("/nav", name="nav") */ public function nav() { $response = $this->render('nav.twig'); $response ->setVary('X-Login',false) ->setSharedMaxAge(500) ->setPublic(); return $response; } Controller action per fragment
  • 81. <?php namespace AppEventListener; use SymfonyComponentHttpKernelEventFilterResponseEvent; class VaryListener { public function onKernelResponse(FilterResponseEvent $event) { $response = $event->getResponse(); $response->setVary('Accept-Language',false); } } src/EventListener/VaryListener.php
  • 82.
  • 83. ✓Navigation page ✓Private page Weak spots Not cached because of stateful content
  • 85. Replace PHP session with JSON Web Tokens
  • 87. eyJzdWIiOiJhZG1pbiIsIm V4cCI6MTQ5NTUyODc1Niwi bG9naW4iOnRydWV9 { "alg": "HS256",  "typ": "JWT" } { "sub": "admin", "exp": 1495528756, "login": true } HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret ) eyJhbGciOiJIUzI1NiIsI nR5cCI6IkpXVCJ9 u4Idy- SYnrFdnH1h9_sNc4OasOR BJcrh2fPo1EOTre8
  • 89. sub jwt { std.log("Ready to perform some JWT magic"); if(cookie.isset("jwt_cookie")) { #Extract header data from JWT 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")); #Don't allow invalid JWT header if(var.get("type") == "JWT" && var.get("algorithm") == "HS256") { #Extract signature & payload data from JWT 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("jti",regsub(var.get("payload"),{"^.*?"jti"s*:s*"([a-z0-9A-Z_-]+)".*?$"},"1")); var.set("userId",regsub(var.get("payload"),{"^.*?"uid"s*:s*"([0-9]+)".*?$"},"1")); var.set("roles",regsub(var.get("payload"),{"^.*?"roles"s*:s*"([a-z0-9A-Z_-, ]+)".*?$"},"1")); #Only allow valid userId if(var.get("userId") ~ "^d+$") { #Don't allow expired JWT if(std.time(var.get("exp"),now) >= now) { #SessionId should match JTI value from JWT if(cookie.get(var.get("sessionCookie")) == var.get("jti")) { #Don't allow invalid JWT signature if(var.get("signature") == var.get("currentSignature")) { #The sweet spot 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: session cookie doesn't match JTI." + var.get("sessionCookie") + ": " + cookie.get(var.get("sessionCookie")) + ", JTI:" + var.get("jti")); } } 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); } #Look for full private content if(req.url ~ "/node/2" && req.url !~ "^/user/login") { if(req.http.X-login != "true") { return(synth(302,"/user/login?destination=" + req.url)); } } } Insert incomprehensible Varnish VCL code here …
  • 90. X-Login: true End result: X-Login: false Custom request header set by Varnish
  • 92. Vary: Accept-Language, X-Login Content for logged-in & anonymous differs
  • 93. <script language="JavaScript">
 function getCookie(name) {
 var value = "; " + document.cookie;
 var parts = value.split("; " + name + "=");
 if (parts.length == 2) return parts.pop().split(";").shift();
 }
 function parseJwt (token) {
 var base64Url = token.split('.')[1];
 var base64 = base64Url.replace('-', '+').replace('_', '/');
 return JSON.parse(window.atob(base64));
 };
 $(document).ready(function(){
 if ($.cookie('token') != null ){
 var token = parseJwt($.cookie("token"));
 $("#usernameLabel").html(', ' + token.sub);
 }
 });
 </script> Parse JWT in Javascript
  • 96. <?php namespace AppService; use FirebaseJWTJWT; use SymfonyComponentHttpFoundationCookie; class JwtAuthentication { protected $key; protected $username; protected $password; public function __construct($key,$username,$password) { $this->key = $key; $this->username = $username; $this->password = $password; } public function jwt($username) { return JWT::encode([ 'sub'=>$username, 'exp'=>time() + (4 * 24 * 60 * 60), 'login'=>true, ],$this->key); } public function createCookie($username) { return new Cookie("token",$this->jwt($username), time() + (3600 * 48), '/', null, false, false); } public function validate($token) { src/Service/JwtAuthentication.php
  • 97. $this->password = $password; } public function jwt($username) { return JWT::encode([ 'sub'=>$username, 'exp'=>time() + (4 * 24 * 60 * 60), //'exp'=>time() + 60, 'login'=>true, ],$this->key); } public function createCookie($username) { return new Cookie("token",$this->jwt($username), time() + (3600 * 48), '/', null, false, false); } public function validate($token) { try { $data = JWT::decode($token,$this->key,['HS256']); $data = (array)$data; if($data['sub'] !== $this->username) { return false; } return true; } catch(UnexpectedValueException $e) { return false; } } } src/Service/JwtAuthentication.php
  • 99. ###> JWT authentication ### JWT_KEY=SlowWebSitesSuck JWT_USERNAME=admin JWT_PASSWORD=$2y$10$431rvq1qS9ewNFP0Gti/o.kBbuMK4zs8IDTLlxm5uzV7cbv8wKt0K ###< JWT authentication ### .env
  • 100. /** * @Route("/login", name="login", methods="GET") */ public function login(Request $request, JwtAuthentication $jwt) { if($jwt->validate($request->cookies->get('token'))) { return new RedirectResponse($this->generateUrl('home')); } $response = $this->render('login.twig',['loginLogoutUrl'=>$this- >generateUrl('login'),'loginLogoutLabel'=>'log_in']); $response ->setSharedMaxAge(500) ->setVary('X-Login',false) ->setPublic(); return $response; } /** * @Route("/login", name="loginpost", methods="POST") */ public function loginpost(Request $request, JwtAuthentication $jwt) { $username = $request->get('username'); $password = $request->get('password'); if(!$username || !$password || getenv('JWT_USERNAME') != $username || ! password_verify($password,getenv('JWT_PASSWORD'))) { return new RedirectResponse($this->generateUrl('login')); } $response = new RedirectResponse($this->generateUrl('home')); $response->headers->setCookie($jwt->createCookie($username)); return $response; } src/Controller/DefaultController.php
  • 101. /** * @Route("/logout", name="logout") */ public function logout() { $response = new RedirectResponse($this->generateUrl('login')); $response->headers->clearCookie('token'); return $response; } src/Controller/DefaultController.php
  • 102. /** * @Route("/nav", name="nav") */ public function nav(Request $request, JwtAuthentication $jwt) { $response = $this->render('nav.twig', [ 'validJwt' => $jwt->validate( $request->cookies->get('token') )]) ->setVary('X-Login',false) ->setMaxAge(100) ->setSharedMaxAge(500) ->setPublic(); return $response; } src/Controller/DefaultController.php
  • 103. <nav class="navbar navbar-default navbar-fixed-side"> <ul class="nav"> <li><a href="{{ url('home') }}">Home</a></li> {% if validJwt == true %} <li><a href="{{ url('logout') }}">{{ 'log_out' | trans }}</a></li> {% else %} <li><a href="{{ url('login') }}">{{ 'log_in' | trans }}</a></li> {% endif %} <li><a href="{{ url('private') }}">Private</a></li> </ul> </nav> templates/nav.twig
  • 104. {% extends "base.twig" %} {% block title %}Login{% endblock %} {% block content %} <div class="page-header"> <h1>{{ 'log_in' | trans }}</h1> </div> <form class="form-horizontal" method="post" action="{{ url('loginpost') }}"> <div class="form-group"> <label for="usernameInput" class="col-sm-2 control-label">{{ 'username' | trans }}</label> <div class="col-sm-6"> <input type="text" name="username" class="form-control" id="usernameInput" placeholder="{{ 'username' | trans }}"> </div> </div> <div class="form-group"> <label for="passwordInput" class="col-sm-2 control-label">{{ 'password' | trans }}</label> <div class="col-sm-6"> <input type="password" name="password" class="form-control" id="passwordInput" placeholder="{{ 'password' | trans }}"> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">{{ 'log_in' | trans }}</button> </div> </div> </form> {% endblock %} templates/login.twig
  • 105.
  • 106.
  • 108. With the proper VCL, Varnish can process the JWT and make cache variations for authenticated & anonymous content