Прототипирование событий, отправленных с сервера HTML5 - неоднозначная ошибка и повторный опрос? - PullRequest
13 голосов
/ 31 января 2012

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

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();
}

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

и JavaScript выглядит следующим образом (выполняется на теле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");
    }
}

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

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

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

EDIT 1 : я думал, что проблема может быть связана с сервером, который я использовал, поэтому я протестировал на установке vanamp 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 очень простым.Также любые ответы на мои два пронумерованных вопроса выше были бы действительно полезны.

Спасибо.

Ответы [ 8 ]

21 голосов
/ 20 февраля 2012

Проблема в вашем php.

При том, как написан ваш php-скрипт, только одно сообщение отправляется за выполнение.Вот как это работает, если вы обращаетесь к файлу 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 секунды.Однако (и я только проверил это в chrome), соединения теперь поддерживаются только в течение 30 секунд.Я буду продолжать выяснять, почему это так, и я опубликую решение, когда найду его, но до тех пор это должно по крайней мере приблизить вас к вашей цели.

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

Редактировать:

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

  1. Если вы можете изменить свой php.ini, измените значение на «max_execution_time».Это позволит всем вашим сценариям запускаться в указанное вами время.
  2. В сценарии, который вы хотите запускать в течение длительного времени, используйте функцию ini_set (ключ, значение), где ключом является 'max_execution_time'и значение - это время в секундах, в течение которого вы хотите, чтобы ваш скрипт работал.
  3. В скрипте, который вы хотите запускать в течение длительного времени, используйте функцию set_time_limit (n), где n - это количество секунд, которое вы хотитеваш скрипт для запуска.
2 голосов
/ 17 сентября 2014

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

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

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

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

1 голос
/ 10 февраля 2012

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

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

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

  1. Поставить в очередь задачу для выполнения следующих шагов:
    1. Если для атрибута readyState задано значение CLOSED, прервать задачу.
    2. Установите для атрибута readyState значение CONNECTING..
    3. Выстрелить простое событие с именем 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 + '<br />';
                    }, false);
                    source.addEventListener('open', function (e) {
                        document.getElementById('output').innerHTML += 'connection opened<br />';
                    }, false);
                    source.addEventListener('error', function (e) {
                        if (source.readyState != CONNECTING) {
                            document.getElementById('output').innerHTML += 'error<br />';
                        }
                    }, false);
                }
                else {
                    alert("Browser doesn't support Server-Sent Events");
                }
            }
0 голосов
/ 27 ноября 2015

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

  1. У меня была та же проблема с Firefox, когда выполнялся тот же код js, что и упомянутый.Используя сервер Nginx и некоторый PHP, который вышел (т. Е. Нет непрерывного цикла), я мог получить сообщения обратно на «Запрос» из Firefox только после выхода из PHP.Запустите PHP как скрипт в PHP.exe, и все будет хорошо на консоли, строки будут напечатаны при сбросе.Тем не менее, Nginx не отправляет данные, пока PHP не завершится.Попытка добавления дополнительных \ r \ n \ r \ n и flush () или ob_flush () не помогла.Нет отправки данных, как показано в журналах Wireshark, только задержанный ответный пакет для GET.

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

Так что это определенно проблема Nginx.

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

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

0 голосов
/ 20 апреля 2014

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

0 голосов
/ 24 октября 2012

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

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

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

0 голосов
/ 22 июня 2012

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

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();
    }
}

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

0 голосов
/ 08 июня 2012

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

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

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

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

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

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

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

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

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

...