HTML5 Server-Sent Events prototyping - ambiguous e

2020-01-29 08:39发布

I'm trying to get to grips with Server-Side Events as they fit my requirements perfectly and seem like they should be simple to implement, however I can't get past a vague error and what looks like the connection repeatedly being closed and re-opened. Everything I have tried is based on this and other tutorials.

The PHP is a single 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()));
?>

and the JavaScript looks like this (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");
    }
}

I have searched around a bit but can't find information on

  1. If Apache needs any special configuration to support server-sent events, and
  2. How I can initiate a push from the server with this kind of setup (e.g. can I simply execute the PHP script from CLI to give a push to the already-connected-browser?)

If I run this JS in Chrome (16.0.912.77) it opens the connection, receives the time, then errors (with no useful information in the error object), then reconnects in 3 seconds and goes through the same process. In Firefox (10.0) I get the same behaviour.

EDIT 1: I thought the issue could be related to the server I was using, so I tested on a vanilla XAMPP install and the same error comes up. Should a basic server configuration be able to handle this without modification / extra configuration?

EDIT 2: The following is an example of output from the browser:

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

Can anyone tell me where this is going wrong? The tutorials I have seen make it look like SSE is very straightforward. Also any answers to my two numbered questions above would be really helpful.

Thanks.

8条回答
beautiful°
2楼-- · 2020-01-29 09:31

The problem is not a server side issue, this all happens on the client and is part of the spec (I know it sounds weird).

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

"When a user agent is to reestablish the connection, the user agent must run the following steps. These steps are run asynchronously, not as part of a task. (The tasks that it queues, of course, are run like normal tasks and not asynchronously.)"

  1. Queue a task to run the following steps:
    1. If the readyState attribute is set to CLOSED, abort the task.
    2. Set the readyState attribute to CONNECTING.
    3. Fire a simple event named error at the EventSource object.

I can't see any need to have an error here, so I have modified your Init function to filter out the error event fired whilst connecting.

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楼-- · 2020-01-29 09:31

There is no actual issue with the code, that I can see. The answer selected as correct, is then, incorrect.

This sums up the behavior mentioned in the question (http://www.w3.org/TR/2009/WD-html5-20090212/comms.html):

"If such a resource (with the correct MIME type) completes loading (i.e. the entire HTTP response body is received or the connection itself closes), the user agent should request the event source resource again after a delay equal to the reconnection time of the event source. This doesn't apply for the error cases that are listed below."

The problem lies with the stream. I've successfully kept a single EventStream open before in perl; just send the appropriate HTTP headers, and start sending stream data; never shutdown the stream server side. The issue is that it seems most HTTP libraries attempt to close the stream after its been opened. This will cause the client to attempt to reconnect to the server, which is fully standard compliant.

This means that it will appear that the problem is solved by running a while loop, for a couple of reasons:

A) The code will continue to send data, as if it were pushing out a large file B) The code (php server) will never have the chance to attempt to close the connection

However, the problem here is obvious: to keep the stream alive, a constant stream of data must be sent. This results in wasteful utilization of resources, and negates any benefits the SSE stream is supposed to provide.

I'm not enough of a php guru to know, but I'd imagine that something in the php server/later in the code is prematurely closing the stream; I had to manipulate the stream at Socket level with Perl to keep it open, since HTTP::Response was closing the connection, and causing the client browser to attempt to re-open the connection. In Mojolicious (another Perl web framework), this can be done by opening a Stream object and setting the timeout to zero, so that the stream never times out.

So, the proper solution here is not to use a while loop; it is to call the appropriate php functions for opening, and keeping open, a php stream.

查看更多
登录 后发表回答