Подтвердить что ты не робот

HTML5 Server-Sent Events прототипирование - неоднозначная ошибка и повторный опрос?

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

PHP - это единственный script:

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');

function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}

$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>

и JavaScript выглядит следующим образом: (run on body load):

function init() {

    var source;
    if (!!window.EventSource) {
        source = new EventSource('events.php');
        source.addEventListener('message', function(e) {
            document.getElementById('output').innerHTML += e.data + '<br />';
        }, false);
        source.addEventListener('open', function(e) {
            document.getElementById('output').innerHTML += 'connection opened<br />';
        }, false);
        source.addEventListener('error', function(e) {
            document.getElementById('output').innerHTML += 'error<br />';
        }, false);
    }
    else {
        alert("Browser doesn't support Server-Sent Events");
    }
}

Я немного искал, но не могу найти информацию о

  • Если Apache нуждается в какой-либо специальной конфигурации для поддержки событий, отправленных сервером, и
  • Как я могу инициировать push с сервера с такой настройкой (например, могу ли я просто выполнить PHP script из CLI, чтобы дать толчок уже подключенному браузеру?)

Если я запустил этот JS в Chrome (16.0.912.77), он откроет соединение, получит время, затем ошибки (без полезной информации в объекте ошибки), затем повторно подключается через 3 секунды и проходит один и тот же процесс. В Firefox (10.0) я получаю такое же поведение.

РЕДАКТИРОВАТЬ 1. Я думал, что проблема может быть связана с сервером, который я использовал, поэтому я протестировал на установке Vanilla XAMPP и возникла такая же ошибка. Должна ли базовая конфигурация сервера справиться с этим без изменения/дополнительной конфигурации?

EDIT 2. Ниже приведен пример вывода из браузера:

connection opened
server time: 01:47:20
error
connection opened
server time: 01:47:23
error
connection opened
server time: 01:47:26
error

Может ли кто-нибудь сказать мне, где это происходит не так? Учебники, которые я видел, делают его похожим на SSE очень просто. Также были бы полезны любые ответы на мои два пронумерованных вопроса.

Спасибо.

4b9b3361

Ответ 1

Проблема заключается в вашем php.

С тем, как написан ваш php script, на одно исполнение отправляется только одно сообщение. То, как это работает, если вы напрямую обращаетесь к файлу php и как это работает, если вы обращаетесь к файлу с помощью EventSource. Поэтому, чтобы ваш PHP скрипт отправлял несколько сообщений, вам нужен цикл.

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');

function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}
while(true) {
  $serverTime = time();
  sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
  sleep(1);
}
?>

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

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

Надеюсь, что это поможет,

Edit:

Чтобы поддерживать соединение открытым для смехотворно долгого времени с php, вам нужно установить max_execution_time (спасибо tomfumb для этого). Это может быть выполнено по крайней мере тремя способами:

  • Если вы можете изменить свой php.ini, измените значение "max_execution_time". Это позволит запускать все ваши скрипты в указанное вами время.
  • В script, который вы хотите запустить в течение длительного времени, используйте функцию ini_set (ключ, значение), где ключ - "max_execution_time", а значение - это время в секундах, в течение которого вы хотите запустить script.
  • В script, который вы хотите запустить в течение длительного времени, используйте функцию set_time_limit (n), где n - количество секунд, которое вы хотите запустить script.

Ответ 2

События, отправленные сервером, просты, когда дело доходит до части Javascript. Прежде всего, многие учебные материалы по SSE в Интернете закрывают свои соединения в серверной части. Будь то PHP или Java-примеры. Это действительно удивительно, потому что то, что вы получаете, это просто другой способ внедрения системы "Ajax Polling" со строго определенной структурой полезной нагрузки (и некоторыми второстепенными функциями, такими как значения повторной попытки клиента, заданные на стороне сервера). Вы можете легко реализовать это с помощью нескольких строк jQuery. Тогда нет необходимости в SSE.

Согласно спецификации SSE, я бы сказал, что повторная попытка не должна быть обычным способом реализации клиентской петли. Для меня SSE - это однопоточный метод потоковой передачи, который опирается на серверный сервер, который не закрывает соединение после нажатия на первые данные клиенту.

В Java полезно использовать спецификацию Servlet3 Async для немедленного освобождения потока запросов и обработки/потоковой передачи в другом потоке. Это работает до сих пор, но все же мне не нравится 30-минутное время соединения для запроса EventSource. Даже я нажимаю данные каждые 5 секунд, соединение будет прекращено через 30 секунд (хром, firefox). Конечно, SSE будет подключаться по умолчанию через 3 секунды, но все же я не думаю, что так оно и должно быть.

Одна из проблем заключается в том, что некоторые фреймворки Java MVC не имеют возможности поддерживать соединение открытым после отправки данных, так что вы заканчиваете кодирование с помощью простого API-интерфейса Servlet. После того, как в 24 часа на прототипе кодирования в Java я более или менее разочарован, потому что усиление по сравнению с традиционным циклом jQuery-Ajax не так много. И проблема с полифорированием функции SSE также существует.

Ответ 3

Проблема не проблема на стороне сервера, все это происходит на клиенте и является частью спецификации (я знаю, это звучит странно).

http://dev.w3.org/html5/eventsource/

"Когда пользовательский агент должен восстановить соединение, пользовательский агент должен выполнить следующие шаги. Эти шаги выполняются асинхронно, а не как часть задачи. (Задачи, которые он ставит в очередь, запускаются, как обычно задачи, а не асинхронно.)"

  •   Задайте очередь для выполнения следующих шагов:   
    •     
    • Если для атрибута readyState установлено значение ЗАКРЫТО, отмените задачу.    
    • Установите для атрибута readyState сообщение CONNECTING.    
    • Опорожнить простую событие с именем error в объекте EventSource.  

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

function init() {
                var CONNECTING = 0;
                var source;
                if (!!window.EventSource) {
                    source = new EventSource('events.php');
                    source.addEventListener('message', function (e) {
                        document.getElementById('output').innerHTML += e.data + '
'; }, false); source.addEventListener('open', function (e) { document.getElementById('output').innerHTML += 'connection opened
'; }, false); source.addEventListener('error', function (e) { if (source.readyState != CONNECTING) { document.getElementById('output').innerHTML += 'error
'; } }, false); } else { alert("Browser doesn't support Server-Sent Events"); } }

Ответ 4

Нет актуальной проблемы с кодом, который я вижу. Ответ, выбранный как правильный, затем неверен.

В этом выражается поведение, упомянутое в вопросе (http://www.w3.org/TR/2009/WD-html5-20090212/comms.html):

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

Проблема заключается в потоке. Я успешно сохранил один EventStream открытым раньше в perl; просто отправьте соответствующие HTTP-заголовки и начните отправлять данные потока; никогда не завершайте работу сервера потокового сервера. Проблема в том, что большинство HTTP-библиотек пытаются закрыть поток после его открытия. Это заставит клиента попытаться подключиться к серверу, который полностью соответствует стандарту.

Это означает, что проблема будет решена путем запуска цикла while по нескольким причинам:

A) Код будет продолжать отправлять данные, как если бы он выталкивал большой файл B) У кода (php server) никогда не будет возможности закрыть соединение

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

Мне не хватает гуру php, но я бы предположил, что что-то на сервере php/позже в коде преждевременно закрывает поток; Мне пришлось манипулировать потоком на уровне Socket с помощью Perl, чтобы он был открыт, поскольку HTTP:: Response закрывал соединение и заставлял браузер клиента пытаться повторно открыть соединение. В Mojolicious (другой веб-фреймворке Perl) это можно сделать, открыв объект Stream и установив тайм-аут на ноль, так что поток никогда не истечет.

Итак, правильное решение здесь - не использовать цикл while; он должен вызвать соответствующие функции php для открытия и сохранения открытого потока php.

Ответ 5

Я смог сделать это, выполнив специальный цикл событий. Похоже, что эта функция html5 совсем не готова и имеет проблемы совместимости даже с последней версией google chrome. Вот он, работая над firefox (не может получить сообщение, отправленное правильно на хром):

var source;

function Body_Load(event) {
    loopEvent();
}

function loopEvent() {
    if (source == undefined) {
        source = new EventSource("event/message.php");
    }
    source.onmessage = function(event) {
        _e("out").value = event.data;
        loopEvent();
    }
}

P.S.: _e - это функция, которая вызывает document.getElementById(id);

Ответ 6

В соответствии с Spec, 3-секундное пересоединение является конструктивным, когда соединение закрыто. PHP с циклом должен теоретически остановить это, но PHP script будет работать бесконечно и тратить ресурсы. Из-за этой проблемы вам следует попытаться избежать использования apache и php для SSE.

Стандартный ответ HTTP должен закрыть соединение после отправки ответа. Вы можете изменить это с помощью заголовка "connection: keep-alive", который должен сообщить браузеру, что соединение предназначено для того, чтобы оставаться открытым, хотя это может вызвать проблемы, если вы используете прокси.

node.js или что-то подобное - это лучший механизм для использования SSE, а не apache/php, и поскольку в основном это JavaScript, его довольно легко понять.

Ответ 7

Событие, отправленное сервером в качестве имени, предполагает, что данные должны перемещаться с сервера на клиент, если он должен повторно подключаться каждые три секунды, чтобы извлекать данные с сервера, тогда он не отличается от других механизмов опроса. Целью SSE является уведомление клиента как в скором времени появятся новые данные, о которых клиент не знает. Поскольку сервер закрывает соединение, даже если заголовок остается в живых, нет другого пути, кроме запуска php script в бесконечном цикле, но со значительным сном потока, чтобы предотвратить нагрузку на сервер. теперь я не вижу другого выхода и его лучше, чем спам-сервер каждые 3 секунды для новых данных.

Ответ 8

Я пытаюсь сделать то же самое. С разной степенью успеха.

  • Имел ту же проблему с Firefox, используя тот же самый js-код, что и упоминалось. Используя сервер Nginx и какой-то PHP, который вышел (т.е. Не был непрерывный цикл), я мог вернуть сообщения к "Request" из firefox только после выхода PHP. Запустите PHP как script в PHP.exe, и все хорошо на коммоне, укусы печатаются при покраске. Однако Nginx не отправляет данные до завершения PHP. Попробовал добавить дополнительные \r\n\r\n и flush() или ob_flush() не помогло. Нет никакого нажатия данных, как показано в журналах Wireshark, всего лишь отложенный пакет ответа в GET.

Прочтите, что мне нужен модуль "push" для Nginx, который требует повторной сборки из исходного кода.

Итак, это определенно проблема Nginx.

  1. Используя сокет в 'C', я смог отправить данные в Firefox, как ожидалось, и сокет был открыт, и никаких сообщений не было пропущено. Однако это имеет тот недостаток, что мне нужно сервер page.html, а события/поток из одного и того же сокета, или firefox не будет подключаться из-за проблем с URL-адресом Cross Site Url. Есть несколько способов обойти это в определенных ситуациях, но не для iframe в системе меню. Этот подход доказал, что SSE работает с firefox, и в журнале wirehark вставляются пакеты. Если в опции 1 были только пакеты запроса/ответа.

Все это сказало, у меня все еще нет решения. Я попытался удалить буферизацию на PHP и Nginx. Но до тех пор, пока PHP не закончится, ничего не останется. Пробовал разные параметры заголовка, например, куски тоже не помогли. Мне не нравится писать полномасштабный http-сервер в "C", но, похоже, это единственный вариант, который работает для меня на данный момент. Я собираюсь попробовать Apache, но большинство писем говорит о том, что это хуже, чем Nginx на этом задании.