SlideShare a Scribd company logo
Developing cacheable
PHP applications
Thijs Feryn
Slow websites suck
Hi, I’m Thijs
I’m
@ThijsFeryn
on Twitter
I’m an
Evangelist
At
I’m an
Evangelist
At
Developing cacheable PHP applications - Confoo 2018
Cache
Don’t
recompute
if the data
hasn’t
changed
Developing cacheable PHP applications - Confoo 2018
Whatifwe
coulddesign
oursoftware
withHTTP
cachingin
mind?
✓Portability
✓Developer empowerment
✓Control
✓Consistent caching behavior
Caching state of mind
Developing cacheable PHP applications - Confoo 2018
Reverse
caching
proxy
Normally
User Server
With ReCaPro *
User ReCaPro Server
* Reverse Caching Proxy
Content Delivery Network
Developing cacheable PHP applications - Confoo 2018
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
Inanidealworld
✓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
Developing cacheable PHP applications - Confoo 2018
Developing cacheable PHP applications - Confoo 2018
Time To Live
Cache variations
Authentication
Legacy
Developing cacheable PHP applications - Confoo 2018
Twig templates
Developing cacheable PHP applications - Confoo 2018
✓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
Developing cacheable PHP applications - Confoo 2018
Developing cacheable PHP applications - Confoo 2018
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, Logger $logger)
{
$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;
}
...
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
Developing cacheable PHP applications - Confoo 2018
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
Developing cacheable PHP applications - Confoo 2018
✓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)
{
if($jwt->validate($request->cookies->get('token'))) {
$loginLogoutUrl = $loginLogoutUrl = $this->generateUrl('logout');
$loginLogoutLabel = 'log_out';
} else {
$loginLogoutUrl = $this->generateUrl('login');
$loginLogoutLabel = 'log_in';
}
$response = $this->render('nav.twig',
['loginLogoutUrl'=>$loginLogoutUrl,'loginLogoutLabel'=>$loginLogoutLabel]);
$response
->setVary('X-Login',false)
->setSharedMaxAge(500)
->setPublic();
return $response;
}
src/Controller/DefaultController.php
{% 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
Developing cacheable PHP applications - Confoo 2018
Developing cacheable PHP applications - Confoo 2018
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 - Confoo 2018

More Related Content

What's hot (20)

PDF
Running php on nginx
Harald Zeitlhofer
 
PDF
LCA2014 - Introduction to Go
dreamwidth
 
PPTX
Advanced HTTP Caching
Martin Breest
 
PPTX
Northeast PHP - High Performance PHP
Jonathan Klein
 
PDF
ApacheConNA 2015: What's new in Apache httpd 2.4
Jim Jagielski
 
PDF
Devoxx Maroc 2015 HTTP 1, HTTP 2 and folks
Nicolas Martignole
 
PDF
HipHop VM: overclocking Symfony
Vadim Borodavko
 
PDF
PyGotham 2014 Introduction to Profiling
Perrin Harkins
 
PPTX
Elastic stack
Minsoo Jun
 
PPTX
Interactive web. O rly?
timbc
 
PDF
Scaling PHP to 40 Million Uniques
Jonathan Klein
 
PPTX
PSGI and Plack from first principles
Perl Careers
 
PPTX
Altitude San Francisco 2018: Programming the Edge
Fastly
 
PDF
AnyMQ, Hippie, and the real-time web
clkao
 
PDF
"Swoole: double troubles in c", Alexandr Vronskiy
Fwdays
 
PDF
Site Performance Optimization for Joomla #jwc13
Hans Kuijpers
 
PPT
Adventures in infrastructure as code
Julian Simpson
 
KEY
Genkidama:実装と課題
Takuya ASADA
 
PDF
HTTP/2, SPDY e Otimizações Web - Front In Maceió 2014 - Sérgio Lopes
Caelum
 
PPTX
June8 presentation
nicobn
 
Running php on nginx
Harald Zeitlhofer
 
LCA2014 - Introduction to Go
dreamwidth
 
Advanced HTTP Caching
Martin Breest
 
Northeast PHP - High Performance PHP
Jonathan Klein
 
ApacheConNA 2015: What's new in Apache httpd 2.4
Jim Jagielski
 
Devoxx Maroc 2015 HTTP 1, HTTP 2 and folks
Nicolas Martignole
 
HipHop VM: overclocking Symfony
Vadim Borodavko
 
PyGotham 2014 Introduction to Profiling
Perrin Harkins
 
Elastic stack
Minsoo Jun
 
Interactive web. O rly?
timbc
 
Scaling PHP to 40 Million Uniques
Jonathan Klein
 
PSGI and Plack from first principles
Perl Careers
 
Altitude San Francisco 2018: Programming the Edge
Fastly
 
AnyMQ, Hippie, and the real-time web
clkao
 
"Swoole: double troubles in c", Alexandr Vronskiy
Fwdays
 
Site Performance Optimization for Joomla #jwc13
Hans Kuijpers
 
Adventures in infrastructure as code
Julian Simpson
 
Genkidama:実装と課題
Takuya ASADA
 
HTTP/2, SPDY e Otimizações Web - Front In Maceió 2014 - Sérgio Lopes
Caelum
 
June8 presentation
nicobn
 

Similar to Developing cacheable PHP applications - Confoo 2018 (20)

PDF
ApacheCon 2014 - What's New in Apache httpd 2.4
Jim Jagielski
 
PDF
Caching the Uncacheable
danrot
 
PDF
Less and faster – Cache tips for WordPress developers
Seravo
 
PPT
Drupal Performance - SerBenfiquista.com Case Study
hernanibf
 
PDF
REST in ( a mobile ) peace @ WHYMCA 05-21-2011
Alessandro Nadalin
 
PDF
What's New and Newer in Apache httpd-24
Jim Jagielski
 
PDF
Apache and PHP: Why httpd.conf is your new BFF!
Jeff Jones
 
PDF
From Web Acceleration to Content Delivery with Varnish - Howest Brugge 2024
Thijs Feryn
 
PDF
WordPress At Scale. WordCamp Dhaka 2019
Anam Ahmed
 
PDF
VUG5: Varnish at Opera Software
Cosimo Streppone
 
PPT
Ajax to the Moon
davejohnson
 
ODP
Caching and tuning fun for high scalability
Wim Godden
 
PDF
Altitude SF 2017: Optimizing your hit rate
Fastly
 
PPTX
Modern javascript localization with c-3po and the good old gettext
Alexander Mostovenko
 
ODP
MNPHP Scalable Architecture 101 - Feb 3 2011
Mike Willbanks
 
PDF
20190516 web security-basic
MksYi
 
PDF
Caching with Varnish
schoefmax
 
PDF
Thijs Feryn - Leverage HTTP to deliver cacheable websites - Codemotion Berlin...
Codemotion
 
PDF
Thijs Feryn - Leverage HTTP to deliver cacheable websites - Codemotion Berlin...
Codemotion
 
PDF
OWASP Top 10 - Checkmarx Presentation at Polytechnic Institute of Cávado and Ave
Checkmarx
 
ApacheCon 2014 - What's New in Apache httpd 2.4
Jim Jagielski
 
Caching the Uncacheable
danrot
 
Less and faster – Cache tips for WordPress developers
Seravo
 
Drupal Performance - SerBenfiquista.com Case Study
hernanibf
 
REST in ( a mobile ) peace @ WHYMCA 05-21-2011
Alessandro Nadalin
 
What's New and Newer in Apache httpd-24
Jim Jagielski
 
Apache and PHP: Why httpd.conf is your new BFF!
Jeff Jones
 
From Web Acceleration to Content Delivery with Varnish - Howest Brugge 2024
Thijs Feryn
 
WordPress At Scale. WordCamp Dhaka 2019
Anam Ahmed
 
VUG5: Varnish at Opera Software
Cosimo Streppone
 
Ajax to the Moon
davejohnson
 
Caching and tuning fun for high scalability
Wim Godden
 
Altitude SF 2017: Optimizing your hit rate
Fastly
 
Modern javascript localization with c-3po and the good old gettext
Alexander Mostovenko
 
MNPHP Scalable Architecture 101 - Feb 3 2011
Mike Willbanks
 
20190516 web security-basic
MksYi
 
Caching with Varnish
schoefmax
 
Thijs Feryn - Leverage HTTP to deliver cacheable websites - Codemotion Berlin...
Codemotion
 
Thijs Feryn - Leverage HTTP to deliver cacheable websites - Codemotion Berlin...
Codemotion
 
OWASP Top 10 - Checkmarx Presentation at Polytechnic Institute of Cávado and Ave
Checkmarx
 
Ad

More from Thijs Feryn (20)

PDF
Optimizing Varnish for Magento: Advanced Techniques for Performance and Scala...
Thijs Feryn
 
PDF
HTTP logging with varnishlog (Brussels PHP 2022)
Thijs Feryn
 
PDF
The Subtle Art Of Cache Invalidation (FOSSingapore 2024)
Thijs Feryn
 
PDF
Best practices for caching Plone with Varnish at large scale - PloneConf Bras...
Thijs Feryn
 
PDF
Varnish caching in Plone - PloneConf Brasilia 2024
Thijs Feryn
 
PDF
Developing Cacheable PHP Applications - PHP SP 2024
Thijs Feryn
 
PDF
Caching the uncacheable in Varnish - SREday London 2024.pdf
Thijs Feryn
 
PDF
Varnish Enterprise - when you need the full power of caching
Thijs Feryn
 
PDF
Accelerate your Kubernetes clusters with Varnish Caching
Thijs Feryn
 
PDF
10 things that helped me advance my career - PHP UK Conference 2024
Thijs Feryn
 
PDF
Distributed load testing with K6 - NDC London 2024
Thijs Feryn
 
PDF
HTTP headers that make your website go faster - devs.gent November 2023
Thijs Feryn
 
PDF
Living on the edge - EBU Horizons 2023
Thijs Feryn
 
PDF
Distributed Load Testing with k6 - DevOps Barcelona
Thijs Feryn
 
PDF
Core web vitals meten om je site sneller te maken - Combell Partner Day 2023
Thijs Feryn
 
PDF
HTTP headers that make your website go faster
Thijs Feryn
 
PDF
HTTP headers that will make your website go faster
Thijs Feryn
 
PDF
Distributed load testing with k6
Thijs Feryn
 
PDF
HTTP logging met Varnishlog - PHPWVL 2022
Thijs Feryn
 
PDF
Taking Laravel to the edge with HTTP caching and Varnish
Thijs Feryn
 
Optimizing Varnish for Magento: Advanced Techniques for Performance and Scala...
Thijs Feryn
 
HTTP logging with varnishlog (Brussels PHP 2022)
Thijs Feryn
 
The Subtle Art Of Cache Invalidation (FOSSingapore 2024)
Thijs Feryn
 
Best practices for caching Plone with Varnish at large scale - PloneConf Bras...
Thijs Feryn
 
Varnish caching in Plone - PloneConf Brasilia 2024
Thijs Feryn
 
Developing Cacheable PHP Applications - PHP SP 2024
Thijs Feryn
 
Caching the uncacheable in Varnish - SREday London 2024.pdf
Thijs Feryn
 
Varnish Enterprise - when you need the full power of caching
Thijs Feryn
 
Accelerate your Kubernetes clusters with Varnish Caching
Thijs Feryn
 
10 things that helped me advance my career - PHP UK Conference 2024
Thijs Feryn
 
Distributed load testing with K6 - NDC London 2024
Thijs Feryn
 
HTTP headers that make your website go faster - devs.gent November 2023
Thijs Feryn
 
Living on the edge - EBU Horizons 2023
Thijs Feryn
 
Distributed Load Testing with k6 - DevOps Barcelona
Thijs Feryn
 
Core web vitals meten om je site sneller te maken - Combell Partner Day 2023
Thijs Feryn
 
HTTP headers that make your website go faster
Thijs Feryn
 
HTTP headers that will make your website go faster
Thijs Feryn
 
Distributed load testing with k6
Thijs Feryn
 
HTTP logging met Varnishlog - PHPWVL 2022
Thijs Feryn
 
Taking Laravel to the edge with HTTP caching and Varnish
Thijs Feryn
 
Ad

Recently uploaded (20)

PPTX
Building Search Using OpenSearch: Limitations and Workarounds
Sease
 
PDF
Meetup Kickoff & Welcome - Rohit Yadav, CSIUG Chairman
ShapeBlue
 
PPTX
Top iOS App Development Company in the USA for Innovative Apps
SynapseIndia
 
PDF
CloudStack GPU Integration - Rohit Yadav
ShapeBlue
 
PDF
LLMs.txt: Easily Control How AI Crawls Your Site
Keploy
 
PDF
SWEBOK Guide and Software Services Engineering Education
Hironori Washizaki
 
PDF
Wojciech Ciemski for Top Cyber News MAGAZINE. June 2025
Dr. Ludmila Morozova-Buss
 
PDF
Rethinking Security Operations - SOC Evolution Journey.pdf
Haris Chughtai
 
PPTX
Extensions Framework (XaaS) - Enabling Orchestrate Anything
ShapeBlue
 
PDF
Ampere Offers Energy-Efficient Future For AI And Cloud
ShapeBlue
 
PPTX
Webinar: Introduction to LF Energy EVerest
DanBrown980551
 
PDF
Why Orbit Edge Tech is a Top Next JS Development Company in 2025
mahendraalaska08
 
PDF
Français Patch Tuesday - Juillet
Ivanti
 
PDF
Windsurf Meetup Ottawa 2025-07-12 - Planning Mode at Reliza.pdf
Pavel Shukhman
 
PDF
How Startups Are Growing Faster with App Developers in Australia.pdf
India App Developer
 
PDF
Blockchain Transactions Explained For Everyone
CIFDAQ
 
PPT
Interview paper part 3, It is based on Interview Prep
SoumyadeepGhosh39
 
PDF
NewMind AI Journal - Weekly Chronicles - July'25 Week II
NewMind AI
 
PDF
Empowering Cloud Providers with Apache CloudStack and Stackbill
ShapeBlue
 
PDF
TrustArc Webinar - Data Privacy Trends 2025: Mid-Year Insights & Program Stra...
TrustArc
 
Building Search Using OpenSearch: Limitations and Workarounds
Sease
 
Meetup Kickoff & Welcome - Rohit Yadav, CSIUG Chairman
ShapeBlue
 
Top iOS App Development Company in the USA for Innovative Apps
SynapseIndia
 
CloudStack GPU Integration - Rohit Yadav
ShapeBlue
 
LLMs.txt: Easily Control How AI Crawls Your Site
Keploy
 
SWEBOK Guide and Software Services Engineering Education
Hironori Washizaki
 
Wojciech Ciemski for Top Cyber News MAGAZINE. June 2025
Dr. Ludmila Morozova-Buss
 
Rethinking Security Operations - SOC Evolution Journey.pdf
Haris Chughtai
 
Extensions Framework (XaaS) - Enabling Orchestrate Anything
ShapeBlue
 
Ampere Offers Energy-Efficient Future For AI And Cloud
ShapeBlue
 
Webinar: Introduction to LF Energy EVerest
DanBrown980551
 
Why Orbit Edge Tech is a Top Next JS Development Company in 2025
mahendraalaska08
 
Français Patch Tuesday - Juillet
Ivanti
 
Windsurf Meetup Ottawa 2025-07-12 - Planning Mode at Reliza.pdf
Pavel Shukhman
 
How Startups Are Growing Faster with App Developers in Australia.pdf
India App Developer
 
Blockchain Transactions Explained For Everyone
CIFDAQ
 
Interview paper part 3, It is based on Interview Prep
SoumyadeepGhosh39
 
NewMind AI Journal - Weekly Chronicles - July'25 Week II
NewMind AI
 
Empowering Cloud Providers with Apache CloudStack and Stackbill
ShapeBlue
 
TrustArc Webinar - Data Privacy Trends 2025: Mid-Year Insights & Program Stra...
TrustArc
 

Developing cacheable PHP applications - Confoo 2018