понедельник, 20 января 2020 г.

HTTP/2 Server Push в Golang

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

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

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

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

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

Например, если сервер знает, что app.js потребуется для отображения страницы, обработчик может инициировать 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, а затем перенаправляет синтетический запрос в обработчик запросов сервера, который сгенерирует отправленный ответ. Второй аргумент для Push указывает дополнительные заголовки для включения в PUSH_PROMISE. Например, если ответ на /app.js зависит от Accept-Encoding, тогда PUSH_PROMISE должен включать значение Accept-Encoding:

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)
        }
    }
    // ...
})

Полностью рабочий пример доступен по адресу https://github.com/golang/blog/blob/master/content/h2push/server/main.go

Если вы запустите сервер (main.go) и загрузите https://localhost:8080, инструменты разработчика вашего браузера должны показать, что app.js и style.css были отправлены сервером.


Начните свои пуши, прежде чем ответить

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

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

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

Когда использовать Server Push

Подумайте об использовании push-сообщений сервера, когда ваша сетевая ссылка простаивает. Только что закончили отправку HTML для вашего веб-приложения? Не тратьте время на ожидание, начните использовать ресурсы, которые понадобятся вашему клиенту. Вы вкладываете ресурсы в свой HTML-файл, чтобы уменьшить задержку? Вместо того, чтобы вставлять, попробуйте push. Переадресация - еще одно хорошее время для использования push, поскольку почти всегда происходит потеря данных в обратном направлении, когда клиент выполняет перенаправление. Существует много возможных сценариев использования push.

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

Вывод

С Go 1.8 стандартная библиотека обеспечивает готовую поддержку HTTP/2 Server Push, предоставляя вам больше возможностей для оптимизации ваших веб-приложений.


Читайте также:


Комментариев нет:

Отправить комментарий