Прототипирование событий, отправленных с сервера HTML5 - неоднозначная ошибка и повторный опрос?
Я пытаюсь разобраться с серверными событиями, так как они идеально соответствуют моим требованиям и кажутся, что они должны быть простыми в реализации, однако я не могу обойти смутную ошибку и то, что соединение постоянно закрывается и перезаписывается. -opened. Все, что я пробовал, основано на этом и других уроках.
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 выглядит так (запускается при загрузке тела):
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 из CLI, чтобы выдать push-сообщение уже подключенному браузеру?)
Если я запускаю этот JS в Chrome (16.0.912.77), он открывает соединение, получает время, затем ошибки (без полезной информации в объекте ошибок), затем повторно соединяется через 3 секунды и проходит тот же процесс. В Firefox (10.0) я получаю такое же поведение.
РЕДАКТИРОВАТЬ 1: Я думал, что проблема может быть связана с сервером, который я использовал, поэтому я проверил на установке vanamp XAMPP, и та же ошибка появляется. Должна ли базовая конфигурация сервера справиться с этим без изменения / дополнительной настройки?
РЕДАКТИРОВАТЬ 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 ответов
Проблема в вашем 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 за это). Это может быть выполнено как минимум тремя способами:
- Если вы можете изменить свой php.ini, измените значение на "max_execution_time". Это позволит всем вашим сценариям работать в течение указанного вами времени.
- В сценарии, который вы хотите запустить в течение длительного времени, используйте функцию ini_set (ключ, значение), где ключом является 'max_execution_time', а значением является время в секундах, для которого вы хотите, чтобы ваш сценарий выполнялся.
- В скрипте, который вы хотите запустить в течение длительного времени, используйте функцию set_time_limit(n), где n - это количество секунд, которое вы хотите, чтобы ваш скрипт запускал.
Отправленные сервером события просты, только если речь идет о части 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 также существует.
Проблема не в стороне сервера, все это происходит на клиенте и является частью спецификации (я знаю, это звучит странно).
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");
}
}
Согласно спецификации, 3-секундное переподключение является расчетным, когда соединение закрыто. PHP с циклом должен теоретически остановить это, но скрипт PHP будет работать бесконечно и тратить ресурсы впустую. Из-за этой проблемы вы должны избегать использования apache и php для SSE.
Стандартный HTTP-ответ должен закрывать соединение после отправки ответа. Вы можете изменить это с помощью заголовка "connection: keep-alive", который должен сообщить браузеру, что соединение должно оставаться открытым, хотя это может вызвать проблемы, если вы используете прокси.
node.js или что-то подобное - это лучший механизм для использования в SSE, а не apache / php, и, поскольку это в основном JavaScript, его довольно легко освоить.
Я пытаюсь то же самое. С разной степенью успеха.
- У меня была та же проблема с 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 на этой работе.
Событие, отправленное сервером, как следует из названия, означает, что данные должны передаваться с сервера на клиент, если ему необходимо каждые три секунды переподключаться для получения данных с сервера, то это ничем не отличается от других механизмов опроса. Цель SSE - оповещать клиента, как только Это новые данные, о которых клиент не знает. Так как сервер закрывает соединение, даже если заголовок остается активным, нет другого способа, кроме как запустить php-скрипт в бесконечном цикле, но со значительным потоком сна, чтобы предотвратить нагрузку на сервер. До сих пор я этого не делаю Посмотрите любой другой выход и его лучше, чем спам-сервер каждые 3 секунды для новых данных.
Там нет фактической проблемы с кодом, который я вижу. Ответ, выбранный как правильный, является неправильным.
Это подводит итог поведения, упомянутого в вопросе (http://www.w3.org/TR/2009/WD-html5-20090212/comms.html):
"Если такой ресурс (с правильным типом MIME) завершает загрузку (т. Е. Получено все тело ответа HTTP или само соединение закрывается), пользовательский агент должен запросить ресурс источника события еще раз после задержки, равной времени повторного подключения источник события. Это не относится к ошибкам, перечисленным ниже."
Проблема заключается в потоке. Я успешно сохранил один EventStream открытым ранее в Perl; просто отправьте соответствующие заголовки HTTP и начните отправку потоковых данных; никогда не выключайте сервер потоковой передачи. Проблема в том, что большинство HTTP-библиотек пытаются закрыть поток после его открытия. Это приведет к тому, что клиент попытается повторно подключиться к серверу, который полностью соответствует стандарту.
Это значит, что проблема будет решена путем запуска цикла while по нескольким причинам:
A) Код будет продолжать отправлять данные, как если бы он выталкивал большой файл. B) Код (php-сервер) никогда не сможет попытаться закрыть соединение.
Однако проблема здесь очевидна: чтобы сохранить поток, необходимо отправить постоянный поток данных. Это приводит к расточительному использованию ресурсов и сводит на нет любые преимущества, которые должен обеспечить поток SSE.
Мне не хватает php-гуру, чтобы знать, но я думаю, что что-то на php-сервере / позже в коде преждевременно закрывает поток; Мне пришлось манипулировать потоком на уровне Socket с помощью Perl, чтобы он оставался открытым, поскольку HTTP::Response закрывал соединение и заставлял браузер клиента пытаться повторно открыть соединение. В Mojolicious (другой веб-платформе Perl) это можно сделать, открыв объект Stream и установив тайм-аут на ноль, чтобы поток никогда не прерывался.
Таким образом, правильным решением здесь не является использование цикла while; это вызов соответствующих функций php для открытия и сохранения потока php.
Я смог сделать это путем реализации пользовательского цикла событий. Кажется, что эта функция 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);