- PVSM.RU - https://www.pvsm.ru -

HTTP-2 Server Push в Go 1.8

Перевод небольшого туториала об использовании HTTP/2 Server Push в стандартной библиотеке Go.

Введение

HTTP/2 был придуман, чтобы решить многие из проблем HTTP/1.x. Современные веб-страницы используют массу дополнительных ресурсов — HTML, скрипты и таблицы стилей, картинки и так далее. В HTTP/1.x каждый из этих ресурсов должен быть запрошен явно отдельным запросом и это может очень замедлять загрузку страницы. Браузер начинает с загрузки HTML, узнаёт про новые необходимые ресурсы по мере разбора страницы. В итоге сервер ожидает пока браузер запросит очередной ресурс и сеть просто простаивает и не используется эффективно.

Чтобы улучшить latency, в HTTP/2 появилась поддержка server push, которая позволяет серверу самому послать ресурсы браузеру ещё до того, как они будут запрошены явно. Часто сервер знает наперёд какие дополнительные ресурсы будут запрошены данной веб-страницей и может начать передавать их вместе с ответом на начальный запрос страницы. Это позволяет серверу максимально эффективно использовать сетевой канал, который бы простаивал в противном случае, и улучшить время загрузки страницы.


HTTP-2 Server Push в Go 1.8 - 1

На уровне протокола, HTTP/2 server push работает с помощью специального типа фреймов — PUSH_PROMISE. Фрейм PUSH_PROMISE описывает запрос, который, по мнению сервера, будет вскоре запрошен браузером. При получении PUSH_PROMISE, браузер знает, что сервер скоро пришлёт этот ресурс. Есть чуть позже браузер затребует этот ресурс, то будет ожидать сервер закончить push, а не инициировать новый HTTP-запрос. Это уменьшает суммарное время, которое браузер тратит на сеть.

Server Push в пакете net/http

В Go 1.8 появилась поддержка server push для http.Server [1]. Эта функция автоматически доступна, если сервер работке в режиме HTTP/2 и входящее соединение также открыто с помощью HTTP/2 протокола. Дальше дело техники — в любом HTTP обработчике вы проверяете, поддерживает ли переменная типа http.ResponseWriter server push простого приведения типа к интерфейсу http.Pusher. [2].

Например, если сервер знает, что скрипт app.js будет нужен для отрисовки страницы, обработчик может принудительно отправить его с помощью server push, если http.Pusher доступен в данном соединении:

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if pusher, ok := w.(http.Pusher); ok {
            // Push поддерживается.
            if err := pusher.Push("/app.js", nil); err != nil {
                log.Printf("Failed to push: %v", err)
            }
        }
        // ...
    })

Метод Push создает новый запрос к /app.js, собирает его во фрейм PUSH_PROMISE и отправляет обработчик запроса сервера, который сгенерирует необходимый ответ клиенту. Второй аргумент метода содержит дополнительные заголовки, если они необходимы для этого фрейма. Например, если ответ для /app.js должен быть с иным Accept-Endoding, то PUSH_PROMISE должен содержать Accept-Endoding заголовок:

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if pusher, ok := w.(http.Pusher); ok {
            // Pushп оддерживается
            options := &http.PushOptions{
                Header: http.Header{
                    "Accept-Encoding": r.Header["Accept-Encoding"],
                },
            }
            if err := pusher.Push("/app.js", options); err != nil {
                log.Printf("Failed to push: %v", err)
            }
        }
        // ...
    })

Полный рабочий пример можно попробовать тут:

$ go get golang.org/x/blog/content/h2push/server

Если вы запустите этот сервер и зайдёте на http://localhost:8080 [3], инспектор сетевых запросов в вашем браузере должен показать, что app.js и style.css были "запушены" сервером.

HTTP-2 Server Push в Go 1.8 - 2

Делайте push вначале ответа

Есть смысл вызывать метод Push до того, как отправлять что-либо в основном ответе. В противном случае есть вариант нечаянно сгенерировать повторяющиеся ответы. Например, представьте, что в вашем обработчике вы пишете в ответ часть HTML кода:

<html>
<head>
    <link rel="stylesheet" href="a.css">...

и затем вызываете Push("a.css", nil). Но браузер, возможно, уже успел распарсить этот фрагмент HTML до того, как получил фрейм PUSH_PROMISE, и в этом случае отправит новый запрос для a.css в дополнение к фрейму. Сервер теперь должен обслужить запрос к a.css дважды. Вызов Push перед тем, как отдавать тело ответа клиенту избавляет от этой проблемы.

Когда использовать server push?

Общий ответ тут — тогда, когда сетевое соединение простаивает. Закончили отправлять HTML веб-приложению? Не тратьте время, начинайте отдавать ресурсы, которые заведомо понадобятся. Возможно вы инлайните ресурсы прямо в HTML, чтобы уменьшить latency? Вместо инлайнинга, попробуйте pushing. Также хороший пример — редиректы страниц, которые почти всегда являются лишним запросом от клиента. Есть масса различных сценариев, где server push может быть полезен — мы только начинаем их осваивать.

Было бы упущением не упомянуть подводные камни. Во-первых, с помощью server push вы можете только отдавать ресурсы, которыми владеет ваш сервер — то есть, ресурсы с других сайтов или CDN отдавать не получится. Второе — не отдавайте ресурсы, если вы не уверены, что клиенту они понадобятся, это будет лишняя трата трафика. Как следствие — избегать отдавать ресурсы, которые, скорее всего, уже получены клиентом и закодированы. И третье — наивный подход "запушить все ресурсы" обычно приводит к ухудшению производительности. Как обычно, в случае сомнений — делайте измерения.

Несколько полезных ссылок для более углублённого понимания:

HTTP/2 Push: The Details [4]
Innovating with HTTP/2 Server Push [5]
Cache-Aware Server Push in H2O [6]
The PRPL Pattern [7]
Rules of Thumb for HTTP/2 Push [8]
Server Push in the HTTP/2 spec [9]

Заключение

В Go 1.8 стандартная библиотека предоставляет функционал HTTP/2 Server Push из коробки, позволяя создавать более эффективные и оптимизированные веб-приложения.

Вы можете посмотреть HTTP/2 Server Push в действии на этой странице [10].

Автор: divan0

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/server-push/251518

Ссылки в тексте:

[1] http.Server: https://golang.org/pkg/net/http/#Server

[2] http.Pusher.: https://golang.org/pkg/net/http/#Pusher

[3] http://localhost:8080: http://localhost:8080

[4] HTTP/2 Push: The Details: https://calendar.perfplanet.com/2016/http2-push-the-details/

[5] Innovating with HTTP/2 Server Push: https://www.igvita.com/2013/06/12/innovating-with-http-2.0-server-push/

[6] Cache-Aware Server Push in H2O: https://github.com/h2o/h2o/issues/421

[7] The PRPL Pattern: https://developers.google.com/web/fundamentals/performance/prpl-pattern/

[8] Rules of Thumb for HTTP/2 Push: https://docs.google.com/document/d/1K0NykTXBbbbTlv60t5MyJvXjqKGsCVNYHyLEXIxYMv0

[9] Server Push in the HTTP/2 spec: https://tools.ietf.org/html/rfc7540#section-8.2

[10] на этой странице: https://http2.golang.org/serverpush

[11] Источник: https://habrahabr.ru/post/325370/