Попереднє завантаження ресурсів та підказок джерел за допомогою HTTP/2 і WebLink

Дата оновлення перекладу 2024-06-10

Попереднє завантаження ресурсів та підказок джерел за допомогою HTTP/2 і WebLink

Symfony надає нативну підтримку (через компонент WebLink) для управління HTTP-заголовками Link, які є ключем до покращення продуктивності додатку при використанні HTTP/2 і можливостей попереднього завантаження сучасних веб-браузерів.

Заголовки Link використовуються у пуші серверу HTTP/2 і підказках джерел W3C для відправки джерел (наприклад, файлів CSS і JavaScript) клієнтам до того, як вони взагалі зрозуміють, що вони їм потрібні. WebLink також включає інші опимізації, які працюють з HTTP 1.x:

  • Попросити браузер вилучити або відобразити іншу веб-сторінку фоново;
  • Провести ранні послідовні пошуки DNS, рукостисканні TCP або перемовини TLS.

Важливо памʼятати, що всі ці функції HTTP/2 вимагають безпечного підключення HTTPS, навіть при роботі на локальній машині. Основні веб-сервери (Apache, nginx, Caddy і т.д.) підтримують це, але ви також можете використати установник і рантайм Docker для Symfony, створений Кевіном Дангласом зі спільноти Symfony.

Попереднє завантаження ресурсів

Уявіть, що у вашому додатку є така веб-сторінка:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>My Application</title>
    <link rel="stylesheet" href="/app.css">
</head>
<body>
    <main role="main" class="container">
        <!-- ... -->
    </main>
</body>
</html>

Дотримуючись традиційного робочого процесу HTTP, коли подається ця сторінка, браузери робитимуть один запит для HTML-сторінки, а другий - для повʼязаного файлу CSS. Однак, завдяки HTTP/2, ваш додаток може почати відправляти зміст СSS-файлу ще до того, як браузер його запитає.

Щоб зробити це, спочатку встановіть компонент WebLink:

1
$ composer require symfony/web-link

Тепер, оновіть шаблон, щоб використовувати функцію Twig preload(), надану WebLink. Атрибут "as" є обовʼязковим, так як браузерам він потрібний для застосування правильної пріоритизації та політики безпеки змісту:

1
2
3
4
<head>
    <!-- ... -->
    <link rel="preload" href="{{ preload('/app.css', { as: 'style' }) }}">
</head>

Якщо ви перезавантажите сторінку, продуктивність покращиться, так як сервер відповів і HTML-сторінкою, і CSS-файлом, хоча браузер запитав лише HTML-сторінку.

Note

Ви можете попередньо завантажити ресурс, огорнувши його у функцію preload():

1
2
3
4
<head>
    <!-- ... -->
    <link rel="preload" href="{{ preload(asset('build/app.css')) }}">
</head>

Крім того, відповідно до специфікації Пріоритетних підказок, ви можете сигналізувати про пріоритет для завантаження, використовуючи атрибут importance:

1
2
3
4
<head>
    <!-- ... -->
    <link rel="preload" href="{{ preload('/app.css', { as: 'style', importance: 'low' }) }}">
</head>

Як це працює?

Компонент WebLink управляє HTTP-заголовками Link, доданими до відповіді. Пи використанні функції preload() у попередньому прикладі, наступний заголовок було додано до відповіді: Link </app.css>; rel="preload"; as="style". Відповідно до специфікації Попереднього завантаження, коли HTTP/2 сервер виявляє, що початковий запит (HTTP 1.x) містить цей HTTP-заголовок, він автоматично запускає відправку для повʼязаного файлу у тому ж зʼєднанні HTTP/2.

Популярні проксі-сервери та CDN, включно з Cloudflare, Fastly і Akamai, також використовують цю функцію. Це означає, що ви можете відправляти джерела клієнтам та покращувати продуктивність ваших додатків у виробництві прямо зараз.

Якщо ви хочете запобігти відправці, але дозволити браузеру попередньо завантажити джерело, випустивший ранній окремий HTTP-запит, використайте опцію nopush:

1
2
3
4
<head>
    <!-- ... -->
    <link rel="preload" href="{{ preload('/app.css', { as: 'style', nopush: true }) }}">
</head>

Підказки джерел

Підказки джерел використовуються додатками, щоб допомогти браузерам вирішити, які джерела мають бути завантажені, попередньо оброблені або підʼєднані у першу чергу.

Компонент WebLink надає наступні функції Twig для відправки цих підказок:

  • dns_prefetch(): "означає першоджерело (наприклад, https://foo.cloudfront.net), яке буде використано для отримання необхідних джерел, які агент користувача має розвʼязати якомога раніше".
  • preconnect(): "означає першоджерело (наприклад, https://www.google-analytics.com), яке буде використано для отримання необхідних джерел. Запуск раннього зʼєднання, який включає в себе послідовний пошук DNS, рукостискання TCP та необовʼязкові перемовини TLS, дозволяє агенту користувача маскувати великі приховані витрати установки підключення".
  • prefetch(): "означає джерело, яке може бути запитане наступною навігацією, і яке агент користувача повинен отримати, щоб агент користувача міг доставити швидшу відповідь, коли джерело буде запитане пізніше".
  • prerender(): "означає джерело, яке може бути запитане наступною навігацією, і яке агенту джерела варто отримати і виконати, щоб агент користувача міг доставити швидшу відповідь, коли джерело буде запитане пізніше".

Даний компонент також підтримує відправку HTTP-посилань, не повʼязаних з продуктивністю, і будь-яких посилань, що реалізують стандарт PSR-13. наприклад, будь-яке посилання, визначене у специфікації HTML:

1
2
3
4
5
<head>
    <!-- ... -->
    <link rel="alternate" href="{{ link('/index.jsonld', 'alternate') }}">
    <link rel="preload" href="{{ preload('/app.css', { as: 'style', nopush: true }) }}">
</head>

Попередній уривок призведе до того, що цей HTTP-заголовок буде відправлено клієнту: Link: </index.jsonld>; rel="alternate",</app.css>; rel="preload"; nopush

Ви також можете додавати посилання в HTTP-запит напряму з контролера та сервісів:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\WebLink\GenericLinkProvider;
use Symfony\Component\WebLink\Link;

class BlogController extends AbstractController
{
    public function index(Request $request): Response
    {
        // використання скорочення addLink(), наданого AbstractController
        $this->addLink($request, new Link('preload', '/app.css'));

        // альтернатива, якщо ви не хочете використовувати скорочення addLink()
        $linkProvider = $request->attributes->get('_links', new GenericLinkProvider());
        $request->attributes->set('_links', $linkProvider->withLink(
            (new Link('preload', '/app.css'))->withAttribute('as', 'style')
        ));

        return $this->render('...');
    }
}