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

Comment utiliser les meilleures pratiques d’HTTP pour mettre en cache votre site web - LaFéWeb 17

Thijs Feryn
November 16, 2017

Comment utiliser les meilleures pratiques d’HTTP pour mettre en cache votre site web - LaFéWeb 17

My very first presentation in French, delivered at a La FéWeb meetup in Louvain-la-Neuve.

More info: https://feryn.eu/speaking/comment-utiliser-les-meilleures-pratiques-dhttp-pour-mettre-en-cache-votre-site-web/

Thijs Feryn

November 16, 2017
Tweet

More Decks by Thijs Feryn

Other Decks in Technology

Transcript

  1. Comment utiliser les
    meilleures pratiques
    d’HTTP pour mettre en
    cache votre site web
    Thijs Feryn

    View Slide

  2. Slow websites suck

    View Slide

  3. Web performance is
    an essential part of
    the user experience

    View Slide

  4. Slow ~ Down

    View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. Identify slowest parts

    View Slide

  9. Reduce the
    impact of the
    code on the
    server

    View Slide

  10. Not just raw speed

    View Slide

  11. Scale

    View Slide

  12. Optimize

    View Slide

  13. After a while you
    hit the limits

    View Slide

  14. Cache

    View Slide

  15. Bonjour tout le monde

    View Slide

  16. Hi, I’m Thijs

    View Slide

  17. I’m
    @ThijsFeryn
    on Twitter

    View Slide

  18. I’m an
    Evangelist
    At

    View Slide

  19. I’m an
    Evangelist
    At

    View Slide

  20. View Slide

  21. View Slide

  22. Cache

    View Slide

  23. Cacher?
    Faire du “caching”?
    Mettre en cache?

    View Slide

  24. Don’t
    recompute
    if the data
    hasn’t
    changed

    View Slide

  25. View Slide

  26. What if we
    could design
    our software
    with HTTP
    caching in
    mind?

    View Slide

  27. View Slide

  28. Reverse
    caching
    proxy

    View Slide

  29. Normally
    User Server

    View Slide

  30. With ReCaPro *
    User ReCaPro Server
    * Reverse Caching Proxy

    View Slide

  31. Content Delivery Network

    View Slide

  32. View Slide

  33. 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

    View Slide

  34. Common
    problems

    View Slide

  35. View Slide

  36. View Slide

  37. Time To Live

    View Slide

  38. Cache variations

    View Slide

  39. Authentication

    View Slide

  40. View Slide

  41. ✓Multi-lingual (Accept-Language)
    ✓Nav
    ✓Header
    ✓Footer
    ✓Main
    ✓Login page & private content

    View Slide




  42. {% block title %}{% endblock %} - Developing cacheable websites







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



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



    {% block content %}{% endblock %}



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




    Base
    template

    View Slide

  43. View Slide

  44. The mission
    Maximum
    Cacheability

    View Slide

  45. Cache-control

    View Slide

  46. View Slide

  47. View Slide

  48. Cache-Control: public, s-maxage=500

    View Slide

  49. Purging

    View Slide

  50. Conditional
    requests

    View Slide

  51. Only fetch
    payload that has
    changed

    View Slide

  52. HTTP/1.1 200 OK

    View Slide

  53. Otherwise:
    HTTP/1.1 304 Not Modified

    View Slide

  54. 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

    View Slide

  55. 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

    View Slide

  56. 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

    View Slide

  57. 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

    View Slide

  58. Cache-Control: public, max-age=100,
    s-maxage=500, stale-while-revalidate=20

    View Slide

  59. Cache-Control: private, no-cache, no-store

    View Slide

  60. session
    cookie
    No cache

    View Slide

  61. View Slide

  62. Code
    renders
    single HTTP
    response

    View Slide

  63. Lowest
    common
    denominator:
    no cache

    View Slide

  64. Block caching

    View Slide


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

    View Slide

  66. Surrogate-Capability: key="ESI/1.0"
    Surrogate-Control: content="ESI/1.0"
    Varnish
    Backend

    Parse ESI placeholders
    Varnish

    View Slide

  67. ESI
    vs
    AJAX

    View Slide

  68. ✓ 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

    View Slide

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

    View Slide

  70. Subrequests

    View Slide


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



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



    {% block content %}{% endblock %}



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



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



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



    {% block content %}{% endblock %}



    {{ render_hinclude(url('footer')) }}


    View Slide









  72. An example page Rendered at 2017-05-17 16:57:14

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris consequat orci eget libero
    sollicitudin,…




    View Slide

  73. Problem:
    no language
    cache
    variation

    View Slide

  74. Vary: Accept-Language

    View Slide

  75. View Slide

  76. View Slide

  77. View Slide

  78. ✓Navigation page
    ✓Private page
    Weak spots
    Not cached
    because of
    stateful content

    View Slide

  79. Move state client-side

    View Slide

  80. Replace PHP session with
    JSON Web Tokens

    View Slide

  81. JWT
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pb
    iIsImV4cCI6MTQ5NTUyODc1NiwibG9naW4iOnRydWV9.u4Idy-
    SYnrFdnH1h9_sNc4OasORBJcrh2fPo1EOTre8
    ✓3 parts
    ✓Dot separated
    ✓Base64 encoded JSON
    ✓Header
    ✓Payload
    ✓Signature (HMAC with secret)

    View Slide

  82. 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

    View Slide

  83. 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)

    View Slide

  84. 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 …

    View Slide

  85. X-Login: true
    End result:
    X-Login: false

    View Slide

  86. Extra cache
    variation
    required

    View Slide

  87. Vary: Accept-Language, X-Login
    Content for logged-in
    & anonymous differs

    View Slide

  88. 
<br/>function getCookie(name) {
<br/>var value = "; " + document.cookie;
<br/>var parts = value.split("; " + name + "=");
<br/>if (parts.length == 2) return parts.pop().split(";").shift();
<br/>}
<br/>function parseJwt (token) {
<br/>var base64Url = token.split('.')[1];
<br/>var base64 = base64Url.replace('-', '+').replace('_', '/');
<br/>return JSON.parse(window.atob(base64));
<br/>};
<br/>$(document).ready(function(){
<br/>if ($.cookie('token') != null ){
<br/>var token = parseJwt($.cookie("token"));
<br/>$("#usernameLabel").html(', ' + token.sub);
<br/>}
<br/>});
<br/>
    Parse JWT
    in Javascript

    View Slide

  89. Does not require
    backend access

    View Slide

  90. View Slide

  91. View Slide

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

    View Slide