Предварительная загрузка ресурсов и подсказок источников с помощью HTTP/2 и WebLink

Дата обновления перевода 2023-01-09

Предварительная загрузка ресурсов и подсказок источников с помощью 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
// src/Controller/BlogController.php
namespace App\Controller;

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

class BlogController extends AbstractController
{
    public function index(Request $request)
    {
        // использование сокращения 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('...');
    }
}