Запросы, инициированные сервером

Я знаю, что HTTP - это протокол запроса-ответа. Короче говоря, моя проблема заключается в том, что клиент делает запрос на сервер, чтобы начать длительный процесс, и я хочу сообщить клиенту о ходе с помощью простого сообщения JSON, содержащего информацию о ходе.

В HTTP/1.1 я знаю, что я могу использовать события, связанные с WebSocket или сервером (SSE) или длительный опрос.

Теперь я знаю, что HTTP/2 еще не поддерживает WebSocket.

Мой вопрос: каков оптимальный способ обработки таких вещей через HTTP/2?

Есть ли какие-либо новые вещи, о которых я не знаю, для обработки запросов, инициированных сервером, в HTTP/2?

Я использую язык Go, если это имеет значение.

Ответ 1

Перед веб-сайтами у нас был опрос. Это буквально означает, что клиент периодически (каждые несколько секунд или любой другой период времени имеет смысл для вашего приложения), сделайте запрос на сервер, чтобы узнать статус задания.

Оптимизация, которую используют многие люди, - это "длинный" опрос. Это предполагает, что сервер принимает запрос и внутренне на сервер, проверяет наличие изменений и спящий режим, пока их нет, до тех пор, пока не будет достигнут определенный тайм-аут или не произойдет желаемое событие, которое затем будет отправлено обратно клиенту.

Если тайм-аут достигнут, соединение закрывается, и клиенту необходимо выполнить другой запрос. Код сервера будет выглядеть примерно так: предположите, что функции делают разумные вещи на основе их имен и подписей:

import (
    "net/http"
    "time"
)

func PollingHandler(w http.ResponseWriter, r *http.Request) {
    jobID := getJobID(r)
    for finish := 60; finish > 0; finish-- { // iterate for ~1 minute
        status, err := checkStatus(jobID)
        if err != nil {
            writeError(w, err)
            return
        }
        if status != nil {
            writeStatus(w, status)
            return
        }
        time.Sleep(time.Second) // sleep 1 second
    }
    writeNil(w) // specific response telling client to request again.
}

Лучшим способом обработки тайм-аута будет использование контекстного пакета и создание контекста с таймаутом. Это выглядит примерно так:

import (
    "net/http"
    "time"
    "golang.org/x/net/context"
)

func PollingHandler(w http.ResponseWriter, r *http.Request) {
    jobID := getJobID(r)
    ctx := context.WithTimeout(context.Background(), time.Second * 60)
    for {
        select{
        case <-ctx.Done():
            writeNil(w)
        default: 
            status, err := checkStatus(jobID)
            if err != nil {
                writeError(w, err)
                return
            }
            if status != nil {
                writeStatus(w, status)
                return
            }
            time.Sleep(time.Second) // sleep 1 second
        }
    }

}

Эта вторая версия вернется в более надежное время, особенно в случае, когда checkStatus может быть более медленным.

Ответ 2

Вы могли бы рассмотреть возможность использования событий HTML/text/event-stream a.k.a. на стороне сервера (SSE). SSE упоминается в вопросе, не будет ли это хорошо работать с http2?

Общие статьи о SSE

(в настоящее время IE является единственным браузером, который не поддерживает SSE)

В следующей статье http2 push сочетается с SSE. Документы вставляются в кеш клиента, и SSE используется для уведомления клиента о том, какие документы могут быть извлечены из его кеша (= инициированные сервером запросы по одному соединению http2):

Основы SSE: на стороне сервера вы начинаете с:

Content-Type: text/event-stream\n\n

Затем для каждого момента, когда вы хотите отправить обновление клиенту, вы отправляете

data: { "name": "value", "othername": "othervalue" }\n\n

Когда закончите, перед закрытием соединения вы можете отправить по электронной почте:

retry: 60000\n\n

чтобы указать браузеру повторить попытку подключения через 60000 мсек

В браузере соединение выполняется следующим образом:

var URL = "http://myserver/myeventstreamer"
if (!!window.EventSource) {
    source = new EventSource(URL);
} else {
    // Resort to xhr polling :(
    alert ("This browser does not support Server Sent Events\nPlease use another browser") 
}

source.addEventListener('message', function(e) {
  console.log(e.data);
}, false);

source.addEventListener('open', function(e) {
  // Connection was opened.
}, false);

source.addEventListener('error', function(e) {
  if (e.readyState == EventSource.CLOSED) {
    // Connection was closed.
  }
}, false);

Ответ 3

Если вы хотите отправить сообщение JSON в виде текста, сервер-отправленное событие (SSE) - это хороший способ сделать это. SSE предназначен для отправки текста. Все данные события кодируются символами UTF-8. Недостатком является то, что это приводит к неэффективности отправки двоичных данных через SSE.

Если вы хотите отправить двоичные данные, вас может заинтересовать механизм Server Push, введенный HTTP/2. Server Push позволяет HTTP/2-серверу отправлять какой-либо файл клиенту по собственной инициативе. Он назывался "Ответ на сервер", даже если он отправлен до того, как клиент запросит его. Клиент автоматически сохраняет файл, отправленный через ответ сервера Push в кеше. Последующий запрос файла немедленно выполняется из кеша без обратной поездки на сервер.

Это эффективный способ переместить двоичные данные в веб-браузер. Заминка заключается в том, что объектная модель документа браузера (DOM) не уведомляется при поступлении ответа на серверный ответ. Браузер обнаруживает, что данные находятся в кеше, когда он делает запрос на него. Мы можем обойти эту проблему следующим образом. Сразу после отправки двоичных данных с помощью Server Push сервер отправляет SSE клиенту, чтобы уведомить его о том, что данные были перенесены в его кеш. Теперь клиент может получить данные из своего кеша, запросив его.

Но пока вы используете SSE, почему бы не отправить файл через SSE в первую очередь? Потому что, если вы имеете дело с двоичными данными, вы можете воспользоваться меньшим размером файла, который позволяет Server Push. Для короткого сообщения JSON может не иметь смысла использовать Server Push. В ситуациях, когда вы нажимаете двоичные данные, и вам нужно сэкономить полосу пропускания, подумайте о отправке данных через Server Push, а затем уведомление SSE.

В отличие от опроса, этот подход не требует периодических запросов от клиента. Сервер может отправлять ответ сервера Push, когда захочет.