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

Webperf: boost your PHP apps with 103 Early Hints

Webperf: boost your PHP apps with 103 Early Hints

Google Chrome, Firefox, Caddy, and Apache now support a new HTTP status code: 103 Early Hints. Since version 6.3, Symfony also supports 103 responses, and FrankenPHP also supports it natively.

This new, rather unusual type of response has a single objective: to speed up the loading time of your websites and applications by allowing the browser to preload the resources it will need to render the page.

In this presentation, we'll look at how 103 differs from other preloading techniques such as HTTP/2+ Server Push or tags, and how to take advantage of it from within our PHP applications.

Kévin Dunglas

October 13, 2023

More Decks by Kévin Dunglas

Other Decks in Programming


  1. 103 Early Hints A new HTTP status code to improve

    webapp performance Photo by Javier Allegue Barros
  2. Kévin Dunglas ➔ Co-founder of Les-Tilleuls.coop ➔ Creator of API

    Platform and FrankenPHP ➔ Contributor to PHP, Go and Caddy server @dunglas
  3. 70+ API, Web and Cloud Experts ➔ 100% employee-owned co-op

    ✊ ➔ API Platform creators 🕷 ➔ PHP Foundation advisory board 🐘 ➔ Symfony backers and core team 🎼 ➔ Laravel official partners 🏎 ➔ [email protected] 💌
  4. Typical Web Page GET /index.html Host: example.com [server think time]

    200 OK Content-Type: text/html; charset=UTF-8 [... rest of the headers ...] <!doctype html> <link rel="stylesheet" href="/common.css"> [... rest of the response body ...] 1st request: 1st response:
  5. Typical Web Page, continued GET /common.css Host: example.com 200 OK

    Content-Type: text/css; charset=UTF-8 [... rest of the headers ...] body { /* … */ } 2nd client request: 2nd response:
  6. 1XX Informational ➔ Informational HTTP responses: 1XX ➔ “Temporary” responses

    sent before the final response ➔ In HTTP since forever ➔ Mostly used for “internal” things handled directly by the browser and servers like file upload (100 Continue) Photo by Possessed Photography
  7. 103 Early Hints Photo by Possessed Photography ➔ 103: new

    HTTP status code for indicating hints ➔ Fetch external resources as early as possible • E.g.: CSS and JS files, images… needed to render a web page ➔ Goal: minimize perceived latency ➔ Rely on pre-existing standards: • Link header (RFC 5988) • Resource hints (HTML standard) • Content Security Policy ➔ HTTP/2 and HTTP/3 connections only
  8. Web Page Using Early Hints GET /index.html Host: example.com 103

    Early Hints Link: </common.css>; rel=preload; as=style [server think time] 200 OK [... typical headers ...] Link: </common.css>; rel=preload; as=style <!doctype html> [... rest of the response body ...] GET /common.css Host: example.com 200 OK Content-Type: text/css; charset=UTF-8 [... rest of the headers ...] body { /* … */ }
  9. What About HTTP/2 Server Push?! ➔ Server Push served the

    same purpose ➔ Server Push has been removed from Chrome • Complex • Rarely used in the wild • “Over-pushing”: only 40% of pushed requests were used ➔ 103 Early Hints requires one more Round-trip time (RTT) but… • “Simple” • Better cache dynamics: if the response is cached, it is not downloaded again
  10. Browser Support ➔ Stable: • Chrome • Edge • Opera

    ➔ Experimental (not enabled by default): • Firefox • Safari ➔ Graceful fallback on “preload” links • All major browsers Photo by Andrew Robulack
  11. Caddy 😍 @hint { protocol http2+ path /index.html } route

    @hint { header Link "</common.css>; rel=preload; as=style" respond 103 }
  12. Apache H2EarlyHints on # If enabled, HTTP/2 Server Push will

    also be used H2Push Off <Location /index.html> H2PushResource /common.css </Location>
  13. PHP FPM Internals Web Browser Web Server (NGINX, Caddy, …)

    HTTP REQUEST HTTP RESPONSE PHP-FPM FASTCGI REQUEST FASTCGI RESPONSE ➔ FastCGI doesn’t support informational responses ➔ 1 FastCGI request = only 1 response
  14. A hack? header('HTTP/1.1 103 Early Hints'); header('Link: </common.css>; rel=preload; as=style');

    // Send the informational request and craft the final one “by hand” echo "HTTP/1.1 200 OK\r\n"; echo "Link: </common.css>; rel=preload; as=style\r\n"; // Other headers echo "\r\n"; echo "Body";
  15. Not a Reliable Hack Photo by Joshua Hoehne ➔ Complex,

    but works as intended if the web server serves the response over HTTP/1.1 ➔ Broken if the web server serves the response over HTTP/2 or HTTP/3 • HTTP/2+ is a binary protocol • Only one response is sent • The handcrafted HTTP/1.1 text of the second response is included “as-is” in the response body of the first response ➔ A hack on top of a hack: • Force the web server to use HTTP/1.1 • Add an HTTP/2+ reverse proxy in front
  16. FrankenPHP Internals ➔ PHP is plugged directly into the web

    server ➔ Send as many HTTP responses as you want Web Browser HTTP REQUEST HTTP RESPONSE
  17. Early Hints with FrankenPHP // Start FrankenPHP: frankenphp php-server header('Link:

    </common.css>; rel=preload; as=style'); headers_send(103); // your slow algorithms and SQL queries 🤪 echo 'Body';
  18. With Laravel $r = new Response(); $r->headers->set('Link', '</common.css>; rel=preload; as=style');

    $r->sendHeaders(103); $r->setContent('My final response'); (and other tools using Symfony HttpFoundation)
  19. With Symfony Full Stack class HomepageController extends AbstractController { #[Route("/",

    name: "homepage")] public function index(): Response { $response = $this->sendEarlyHints([ (new Link(href: '/common.css'))->withAttribute('as', 'stylesheet'), ]); // prepare the contents of the response... return $this->render('homepage/index.html.twig', response: $response); } }