Как на самом деле работают отправленные сервером события? - PullRequest
35 голосов
/ 03 октября 2011

Итак, я понимаю концепцию событий, отправляемых сервером (EventSource):

  • Клиент подключается к конечной точке через EventSource
  • Клиент просто слушает сообщения, отправленные с конечной точки

Меня смущает то, как это работает на сервере. Я посмотрел на разные примеры, но на ум приходит Мозилла: http://hacks.mozilla.org/2011/06/a-wall-powered-by-eventsource-and-server-sent-events/

Теперь это может быть просто плохой пример, но, как я понимаю, вроде бы имеет смысл, как будет работать серверная сторона:

  • Что-то меняется в хранилище данных, например в базе данных
  • Серверный скрипт опрашивает хранилище данных каждую N-ю секунду
  • Если скрипт опроса замечает изменение, клиенту отправляется событие, отправленное сервером

Это имеет смысл? Так ли это на самом деле с точки зрения босоножек?

1 Ответ

50 голосов
/ 17 августа 2012

Сайт доктора HTML5 имеет отличную запись о событиях, отправленных сервером, но я также постараюсь предоставить (разумно) краткое резюме здесь.

Отправляемые сервером события по своей сути - это длительное http-соединение, специальный тип MIME (text/event-stream) и пользовательский агент, который предоставляет API EventSource. Вместе они создают основу однонаправленного соединения между сервером и клиентом, где сообщения могут отправляться с сервера на клиент .

На стороне сервера это довольно просто. Все, что вам действительно нужно сделать, это установить следующие заголовки http:

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

Обязательно ответьте кодом 200, а не 204 или любым другим кодом, поскольку это приведет к отключению совместимых пользовательских агентов. Кроме того, убедитесь, что не разорвать соединение на стороне сервера. Теперь вы можете свободно отправлять сообщения по этому соединению. В nodejs (с использованием Express) это может выглядеть примерно так:

app.get("/my-stream", function(req, res) {
    res.status(200)
       .set({ "content-type"  : "text/event-stream"
            , "cache-control" : "no-cache"
            , "connection"    : "keep-alive"
            })

    res.write("data: Hello, world!\n\n")
})

На клиенте вы просто используете EventSource API, как вы заметили:

var source = new EventSource("/my-stream")
source.addEventListener("message", function(message) {
    console.log(message.data)
})

И это все, в принципе.

Теперь, на практике, здесь происходит то, что соединение поддерживается сервером и клиентом посредством взаимного договора. Сервер будет поддерживать соединение так долго, как считает нужным. При желании он может разорвать соединение и ответить 204 No Content в следующий раз, когда клиент попытается соединиться. Это заставит клиента перестать пытаться переподключиться. Я не уверен, есть ли способ завершить соединение таким образом, чтобы клиент вообще не подключался, тем самым пропуская клиент, пытающийся подключиться один раз.

Как уже упоминалось, клиент также будет поддерживать соединение и попытаться восстановить соединение, если оно было сброшено. Алгоритм переподключения указан в spec и довольно прост.

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

  • id - идентификатор сообщения
  • данные - фактические данные
  • событие - тип события
  • retry - пользовательские агенты должны ждать в миллисекундах перед повторной попыткой сбойного соединения

Любые другие ключи следует игнорировать. Затем сообщения разделяются с помощью двух символов новой строки: \n\n

Следующее является действительным сообщением: (последние символы новой строки добавлены для многословия)

data: Hello, world!
\n

Клиент увидит это как: Hello, world!.

Как это:

data: Hello,
data: world!
\n

Клиент увидит это как: Hello,\nworld!.

Это в значительной степени подводит итог того, что представляют собой отправленные сервером события: длительное не кэшированное http-соединение, тип MIME и простой API-интерфейс javascript.

Для получения дополнительной информации я настоятельно рекомендую прочитать спецификацию . Он небольшой и хорошо описывает вещи (хотя требования к стороне сервера можно было бы обобщить чуть лучше). Я настоятельно рекомендую прочитать его для ожидаемого поведения с некоторыми кодами состояния http, например.

...