HTTP/2 Server Push в Go 1.8

https://blog.golang.org/h2push
  • Перевод

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


Введение


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


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



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


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


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


Например, если сервер знает, что скрипт 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, инспектор сетевых запросов в вашем браузере должен показать, что app.js и style.css были "запушены" сервером.



Делайте 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
Innovating with HTTP/2 Server Push
Cache-Aware Server Push in H2O
The PRPL Pattern
Rules of Thumb for HTTP/2 Push
Server Push in the HTTP/2 spec


Заключение


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


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

  • +22
  • 9,4k
  • 7
Поделиться публикацией

Похожие публикации

Комментарии 7

    –1
    Еще один повод для изучения Go!)
      +3
      во всех этих server push меня смущает только пару вопросов:
      1) клиенту может это все и не нужно (мобильный интернет)
      2) у клиента уже есть эти данные закешированные, все что ему хочется знать это 304 NOT MODIFIED

      не могу понять почему многие текущие разработки идут по принципу:
      1) у пользователя простаивает cpu, надо его загрузить хоть чем-то
      2) у пользователя простаивает канал в интернет, давайте мы по push закешируем весь сайт и пару поддоменов в нагрузку, может понадобится
        0

        Мне кажется эти моменты в конце статьи озвучены.

          +1
          проблема в том, что очень часто сталкиваюсь с позицией «есть возможность — давайте использовать»
          другая ситуация что 51% это возможно и надо, поэтому оставшиеся 49% тоже будут страдать, так как врубается на всех сразу

          уверен многие последний абзац даже не будут читать, так как по всей статье идет внушение что server push это отличнейшая технология которую просто обязан внедрить каждый, кто хочет уменьшить скорость загрузки страницы
            +1

            Ну, как по мне, статья практически по учебнику написана — введение, объяснение зачем это надо, пример использования в Go с кодом и объяснением, разбор когда это нужно, а когда нет, предостережения и ссылки в конце.


            Если вам кажется, что статья написана с позиции "есть возможность — надо использовать", можете посоветовать, как лучше написать её, чтобы не было такой позиции? Я могу передать фидбек автору. Спасибо.

              0
              server push тут ни при чём, никто раньше не мешал делать iframe на контент который нужно будет загрузить клиенту.
              +3

              Эти моменты озвучены, но никакого решения не предусматривается. Я не знаю, как Google оценивает эффективность от внедрения подобных методов, но бьюсь об заклад, что если эта штука взлетит, то 95% сайтов будут тупо пушить все. Потому что умные и сложные решения не приживаются, 95% сайтов делается по принципу «тяп-ляп и в продакшн».


              У меня это больное место, потому что я сейчас в Непале, где дорогой, ненадежный и медленный интернет. И я вижу, как даже мобильные версии сайтов, «мобильные» только в том смысле, что адаптированы под небольшой экран (да и то с оговорками). С точки зрения же потребления трафика, они грузят просто десятки мегабайт ненужного мусора.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое