Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Developing cacheable PHP applications

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Developing cacheable PHP applications

This talk was delivered at the Drupal NYC meetup on July 22nd.

See https://feryn.eu/speaking/developing-cacheable-php-applications-2/ for more details

Avatar for Thijs Feryn

Thijs Feryn

July 22, 2019
Tweet

More Decks by Thijs Feryn

Other Decks in Technology

Transcript

  1. WE'RE HOSTING A MEETUP ✓ THURSDAY JULY 25TH ✓ 6PM

    - 9PM ✓ BARCADE, CHELSEA, NYC ✓ RSVP ! https://info.varnish-software.com/varnish-nyc-meetup-2019
  2. HTTP CACHING MECHANISMS Expires: Sat, 23 July 2019 00:30:00 GMT

    Cache-control: public, max-age=3600, s-maxage=86400 Cache-control: private, no-cache, no-store
  3. ✓STATELESS ✓WELL-DEFINED TTL ✓CACHE / NO-CACHE PER RESOURCE ✓CACHE VARIATIONS

    ✓CONDITIONAL REQUESTS ✓PLACEHOLDERS FOR NON-CACHEABLE CONTENT ✓EDGE-SIDE LOGIC FOR PERSONALIZED CACHING IN AN IDEAL WORLD
  4. <?php namespace App\Controller; use Symfony\Component\HttpFoundation\Request; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class

    DefaultController extends Controller { /** * @Route("/", name="home") */ public function index() { return $this ->render('index.twig') ->setSharedMaxAge(500) ->setPublic(); } }
  5. /** * @Route("/private", name="private") */ public function private() { $response

    = $this ->render('private.twig') ->setPrivate(); $response->headers->addCacheControlDirective('no-store'); return $response; }
  6. 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
  7. CONDITIONAL REQUESTS HTTP/1.1 304 Not Modified Host: localhost Etag: 7c9d70604c6061da9bb9377d3f00eb27

    GET / HTTP/1.1 Host: localhost If-None-Match: 7c9d70604c6061da9bb9377d3f00eb27
  8. 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
  9. CONDITIONAL REQUESTS HTTP/1.1 304 Not Modified Host: localhost Last-Modified: Fri,

    22 Jul 2016 10:11:16 GMT GET / HTTP/1.1 Host: localhost If-Last-Modified: Fri, 22 Jul 2016 10:11:16 GMT
  10. <?php namespace App\EventListener; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use

    Symfony\Component\HttpKernel\Event\FilterResponseEvent; use SymfonyBundles\RedisBundle\Redis\Client as RedisClient; class ConditionalRequestListener { protected $redis; 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
  11. { $this->redis = $redis; } 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
  12. EDGE SIDE INCLUDES ✓ PLACEHOLDER ✓ W3C STANDARD ✓ PROCESSED

    "ON THE EDGE" (E.G. VARNISH) ✓ OUTPUT IS A COMPOSITION OF BLOCKS ✓ STATE PER BLOCK ✓ TTL PER BLOCK <esi:include src="/header" />
  13. EDGE SIDE INCLUDES ✓ SERVER-SIDE ✓ STANDARDIZED ✓ PROCESSED ON

    THE “EDGE”, NOT IN THE BROWSER ✓ GENERALLY FASTER - SEQUENTIAL 
 (ONLY PARALLEL IN ENTERPRISE VERSION) - ONE FAILS, ALL FAIL - LIMITED IMPLEMENTATION IN VARNISH
  14. AJAX ✓ CLIENT-SIDE ✓ COMMON KNOWLEDGE ✓ PARALLEL PROCESSING ✓

    GRACEFUL DEGRADATION - PROCESSED BY THE BROWSER - EXTRA ROUNDTRIPS - SOMEWHAT SLOWER
  15. /** * @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
  16. <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>
  17. <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>
  18. WHAT IF THE CONTENT OF A URL VARIES BASED ON

    THE VALUE OF A REQUEST HEADER?
  19. CACHE VARIATIONS HTTP/1.1 200 OK Host: localhost Content-Language: en Content-type:

    text/html; charset=UTF-8 Hello world output GET / HTTP/1.1 Host: localhost Accept-Language: en, nl, de
  20. sub vcl_recv { if (req.url ~ "^/status\.php$" || req.url ~

    "^/update\.php$" || req.url ~ "^/admin$" || req.url ~ "^/admin/.*$" || req.url ~ "^/flag/.*$" || req.url ~ "^.*/ajax/.*$" || req.url ~ "^.*/ahah/.*$") { return (pass); } } URL blacklist example
  21. vcl 4.0; sub vcl_recv { if (req.http.Cookie) { set req.http.Cookie

    = ";" + req.http.Cookie; set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]+|SSESS[a-z0-9]+|NO_CACHE)=", "; \1="); set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); if (req.http.Cookie == "") { unset req.http.Cookie; } else { return (pass); } } } Cookie example
  22. VARNISH ENTERPRISE ✓ CLIENT SSL TERMINATION ✓ BACKEND SSL CONNECTIONS

    ✓ PARALLEL ESI ✓ MASSIVE STORAGE ENGINE ✓ ENCRYPTION ✓ THROTTLING ✓ RATE LIMITING ✓ PREFETCHING ✓ GEOLOCATION ✓ AUTHENTICATION ✓ EDGESTASH ✓ CUSTOM STATISTICS ✓ ADMIN MODULE ✓ SUPPORT ✓ HIGH AVAILABILITY
  23. HTTP/1.1 200 OK Accept-Ranges: bytes Age: 11 Cache-Control: max-age=30, public,

    s-maxage=100 Content-Type: text/html; charset=UTF-8 Date: Thu, 11 Jul 2019 13:47:57 GMT X-Varnish: 262420 262415 Hello Thijs Hello {{username}} USER VARNISH ENTERPRISE ORIGIN GET /liked HTTP/1.1 Cookie:PHPSESSID=21779b3a7a75fca1851954b9810faa26 GET sf_s21779b3a7a75fca1851954b9810faa26
  24. WE'RE HOSTING A MEETUP ✓ THURSDAY JULY 25TH ✓ 6PM

    - 9PM ✓ BARCADE, CHELSEA, NYC ✓ RSVP ! https://info.varnish-software.com/varnish-nyc-meetup-2019