Slide 1

Slide 1 text

103 Early Hints A new HTTP status code to improve webapp performance Photo by Javier Allegue Barros

Slide 2

Slide 2 text

Kévin Dunglas ➔ Co-founder of Les-Tilleuls.coop ➔ Creator of API Platform and FrankenPHP ➔ Contributor to PHP, Go and Caddy server @dunglas

Slide 3

Slide 3 text

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] 💌

Slide 4

Slide 4 text

The Problem

Slide 5

Slide 5 text

Typical Web Page © Google

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

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 ...] [... rest of the response body ...] 1st request: 1st response:

Slide 8

Slide 8 text

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:

Slide 9

Slide 9 text

Early Hints

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

103 Early Hints © Google

Slide 15

Slide 15 text

Web Page Using Early Hints GET /index.html Host: example.com 103 Early Hints Link: ; rel=preload; as=style [server think time] 200 OK [... typical headers ...] Link: ; rel=preload; as=style [... 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 { /* … */ }

Slide 16

Slide 16 text

Benefits: ~30% LCP improvement © Google

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Using Early Hints

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Caddy 😍 @hint { protocol http2+ path /index.html } route @hint { header Link "; rel=preload; as=style" respond 103 }

Slide 22

Slide 22 text

Apache H2EarlyHints on # If enabled, HTTP/2 Server Push will also be used H2Push Off H2PushResource /common.css

Slide 23

Slide 23 text

NGINX

Slide 24

Slide 24 text

Programmatically With PHP

Slide 25

Slide 25 text

Houston, we have a problem

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

A hack? header('HTTP/1.1 103 Early Hints'); header('Link: ; 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: ; rel=preload; as=style\r\n"; // Other headers echo "\r\n"; echo "Body";

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

A Better Solution

Slide 30

Slide 30 text

FrankenPHP Internals ➔ PHP is plugged directly into the web server ➔ Send as many HTTP responses as you want Web Browser HTTP REQUEST HTTP RESPONSE

Slide 31

Slide 31 text

Early Hints with FrankenPHP // Start FrankenPHP: frankenphp php-server header('Link: ; rel=preload; as=style'); headers_send(103); // your slow algorithms and SQL queries 🤪 echo 'Body';

Slide 32

Slide 32 text

With Laravel $r = new Response(); $r->headers->set('Link', '; rel=preload; as=style'); $r->sendHeaders(103); $r->setContent('My final response'); (and other tools using Symfony HttpFoundation)

Slide 33

Slide 33 text

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); } }

Slide 34

Slide 34 text

Thank you! ➔ Any questions ? les-tilleuls.coop [email protected] @coopTilleuls