Permalink
Browse files

GET + Last-Event-ID header

  • Loading branch information...
1 parent c6ed148 commit 22d5f230a05f09700387757370e152350a0e176a @Yaffle committed Jan 24, 2013
Showing with 103 additions and 83 deletions.
  1. +24 −7 README.md
  2. +16 −11 eventsource.js
  3. +10 −5 php/events.php
  4. +16 −11 tests/eventsource.js
  5. +37 −49 tests/server.js
View
@@ -19,7 +19,8 @@ EventSource polyfill - http://www.w3.org/TR/eventsource/
Server-side requirements:
-------------------------
- * "Last-Event-ID" is sent in POST body (CORS + "Last-Event-ID" header is not supported by all browsers)
+ * "Last-Event-ID" is sent in a query string (CORS + "Last-Event-ID" header is not supported by all browsers)
+ * Answer to preflight request is required (see example)
* It is required to send two kilobyte padding for IE at the top of the response stream - see http://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx?PageIndex=1
* you need to send "comment" message each 15-30 seconds
* do not use the null character, some browsers have problems with it
@@ -39,7 +40,7 @@ EventSource polyfill - http://www.w3.org/TR/eventsource/
--------------------------------------------------------------------------------
CORS
* Firefox 11
- * https://bugs.webkit.org/show_bug.cgi?id=61862 (Chrome 25?)
+ * Chrome 26 (WebKit 537.27)
* Opera 12
lastEventId shouldn't be set when connection dropped without data dispatch - http://www.w3.org/Bugs/Public/show_bug.cgi?id=13761
@@ -75,6 +76,17 @@ http.createServer(function (req, res) {
var t = 0;
if (req.url.indexOf('/events') === 0) {
+ if (req.method === "OPTIONS") {
+ res.writeHead(200, {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "GET",
+ "Access-Control-Allow-Headers": "Last-Event-ID, Cache-Control",
+ "Access-Control-Max-Age": "86400"
+ });
+ res.end();
+ return;
+ }
+
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
@@ -109,6 +121,14 @@ or use PHP (see php/events.php)
```php
<?
+ if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
+ header('Access-Control-Allow-Origin: *');
+ header('Access-Control-Allow-Methods: GET');
+ header('Access-Control-Allow-Headers: Last-Event-ID, Cache-Control');
+ header('Access-Control-Max-Age: 86400');
+ exit();
+ }
+
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Access-Control-Allow-Origin: *');
@@ -122,11 +142,8 @@ or use PHP (see php/events.php)
for ($i = 0; $i < ob_get_level(); $i++) { ob_end_flush(); }
ob_implicit_flush(1);
- // getting last-event-id from POST or from http headers
- $postData = @file_get_contents('php://input');
- parse_str($postData, $tmp);
- if (isset($tmp['Last-Event-ID'])) {
- $lastEventId = $tmp['Last-Event-ID'];
+ if (isset($_GET['lastEventId'])) {
+ $lastEventId = $_GET['lastEventId'];
} else {
$lastEventId = @$_SERVER["HTTP_LAST_EVENT_ID"];
}
View
@@ -116,6 +116,7 @@
var CLOSED = 2;
var contentTypeRegExp = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i;
var webkitBefore535 = /AppleWebKit\/5([0-2][0-9]|3[0-4])[^\d]/.test(navigator.userAgent);
+ var endOfLine = /[\r\n]/;
function getDuration(value, def) {
var n = Number(value);
@@ -156,6 +157,7 @@
var lastEventIdBuffer = "";
var eventTypeBuffer = "";
var responseBuffer = [];
+ var wasCR = false;
options = null;
@@ -199,7 +201,14 @@
wasActivity = true;
}
var i = 0;
- while ((i = part.indexOf("\n")) !== -1) {
+ while ((i = part.search(endOfLine)) !== -1) {
+ var c = part.slice(i, i + 1);
+ if (wasCR && i === 0 && c === "\n") {
+ wasCR = false;
+ part = part.slice(i + 1);
+ continue;
+ }
+ wasCR = c === "\r";
responseBuffer.push(part.slice(0, i));
var field = responseBuffer.join("");
responseBuffer.length = 0;
@@ -343,9 +352,10 @@
eventTypeBuffer = "";
lastEventIdBuffer = lastEventId;//resets to last successful
responseBuffer.length = 0;
+ wasCR = false;
// with GET method in FF xhr.onreadystatechange with readyState === 3 does not work + POST = no-cache
- xhr.open("POST", url, true);
+ xhr.open("GET", url + ((url.indexOf("?") === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(lastEventId) + "&r=" + String(Math.random()).slice(2)), true);
// withCredentials should be setted after "open" for Safari and Chrome (< 19 ?)
xhr.withCredentials = withCredentials;
@@ -354,18 +364,13 @@
if (isXHR) {
// Request header field Cache-Control is not allowed by Access-Control-Allow-Headers.
- //xhr.setRequestHeader("Cache-Control", "no-cache");
-
- // http://code.google.com/p/chromium/issues/detail?id=71694
- xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("Accept", "text/event-stream");
-
// Request header field Last-Event-ID is not allowed by Access-Control-Allow-Headers.
- //if (lastEventId !== "") {
- // xhr.setRequestHeader("Last-Event-ID", lastEventId);
- //}
+ xhr.setRequestHeader("Last-Event-ID", lastEventId);
}
- xhr.send(lastEventId !== "" ? "Last-Event-ID=" + encodeURIComponent(lastEventId) : "");
+
+ xhr.send(null);
}
EventTarget.call(this);
View
@@ -1,5 +1,13 @@
<?
+ if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
+ header('Access-Control-Allow-Origin: *');
+ header('Access-Control-Allow-Methods: GET');
+ header('Access-Control-Allow-Headers: Last-Event-ID, Cache-Control');
+ header('Access-Control-Max-Age: 86400');
+ exit();
+ }
+
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Access-Control-Allow-Origin: ' . @$_SERVER['HTTP_ORIGIN']);
@@ -14,11 +22,8 @@
for ($i = 0; $i < ob_get_level(); $i++) { ob_end_flush(); }
ob_implicit_flush(1);
- // getting last-event-id from POST or from http headers
- $postData = @file_get_contents('php://input');
- parse_str($postData, $tmp);
- if (isset($tmp['Last-Event-ID'])) {
- $lastEventId = $tmp['Last-Event-ID'];
+ if (isset($_GET['lastEventId'])) {
+ $lastEventId = $_GET['lastEventId'];
} else {
$lastEventId = @$_SERVER["HTTP_LAST_EVENT_ID"];
}
View
@@ -116,6 +116,7 @@
var CLOSED = 2;
var contentTypeRegExp = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i;
var webkitBefore535 = /AppleWebKit\/5([0-2][0-9]|3[0-4])[^\d]/.test(navigator.userAgent);
+ var endOfLine = /[\r\n]/;
function getDuration(value, def) {
var n = Number(value);
@@ -156,6 +157,7 @@
var lastEventIdBuffer = "";
var eventTypeBuffer = "";
var responseBuffer = [];
+ var wasCR = false;
options = null;
@@ -199,7 +201,14 @@
wasActivity = true;
}
var i = 0;
- while ((i = part.indexOf("\n")) !== -1) {
+ while ((i = part.search(endOfLine)) !== -1) {
+ var c = part.slice(i, i + 1);
+ if (wasCR && i === 0 && c === "\n") {
+ wasCR = false;
+ part = part.slice(i + 1);
+ continue;
+ }
+ wasCR = c === "\r";
responseBuffer.push(part.slice(0, i));
var field = responseBuffer.join("");
responseBuffer.length = 0;
@@ -343,9 +352,10 @@
eventTypeBuffer = "";
lastEventIdBuffer = lastEventId;//resets to last successful
responseBuffer.length = 0;
+ wasCR = false;
// with GET method in FF xhr.onreadystatechange with readyState === 3 does not work + POST = no-cache
- xhr.open("POST", url, true);
+ xhr.open("GET", url + ((url.indexOf("?") === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(lastEventId) + "&r=" + String(Math.random()).slice(2)), true);
// withCredentials should be setted after "open" for Safari and Chrome (< 19 ?)
xhr.withCredentials = withCredentials;
@@ -354,18 +364,13 @@
if (isXHR) {
// Request header field Cache-Control is not allowed by Access-Control-Allow-Headers.
- //xhr.setRequestHeader("Cache-Control", "no-cache");
-
- // http://code.google.com/p/chromium/issues/detail?id=71694
- xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("Accept", "text/event-stream");
-
// Request header field Last-Event-ID is not allowed by Access-Control-Allow-Headers.
- //if (lastEventId !== "") {
- // xhr.setRequestHeader("Last-Event-ID", lastEventId);
- //}
+ xhr.setRequestHeader("Last-Event-ID", lastEventId);
}
- xhr.send(lastEventId !== "" ? "Last-Event-ID=" + encodeURIComponent(lastEventId) : "");
+
+ xhr.send(null);
}
EventTarget.call(this);
View
@@ -116,10 +116,10 @@ function onTest(response, lastEventId, test, cookies) {
}
function eventStream(request, response) {
- var post = '';
var lastEventId = '';
var test = Number(URL.parse(request.url, true).query.test);
var cookies = {};
+ var u = URL.parse(request.url, true);
(request.headers.cookie || '').split(';').forEach(function (cookie) {
cookie = cookie.split('=');
@@ -135,63 +135,41 @@ function eventStream(request, response) {
response.write(':\n');
}
- function onRequestEnd() {
- var p = querystring.parse(post);
- var headers = {
- 'Content-Type': 'text/event-stream',
- 'Cache-Control': 'no-cache',
- 'Access-Control-Allow-Origin': '*'
- };
- if (test === 9) {
- headers['Access-Control-Allow-Credentials'] = 'true';
- }
-
- response.writeHead(200, headers);
- lastEventId = +request.headers['last-event-id'] || +p['Last-Event-ID'] || 0;
-
- // 2 kb comment message for XDomainRequest (IE8, IE9)
- response.write(':' + Array(2049).join(' ') + '\n');
- response.write('retry: 1000\n');
- response.write('retryLimit: 60000\n');
- response.write('heartbeatTimeout: ' + heartbeatTimeout + '\n');//!
-
- if (!isNaN(test)) {
- onTest(response, lastEventId, test, cookies);
- } else {
- emitter.addListener('message', sendMessages);
- emitter.setMaxListeners(0);
- sendMessages();
- }
- }
-
response.socket.on('close', function () {
emitter.removeListener('message', sendMessages);
- request.removeListener('end', onRequestEnd);
response.end();
});
- request.addListener('data', function (data) {
- if (post.length < 256) {
- post += data;
- }
- });
-
- request.addListener('end', onRequestEnd);
response.socket.setTimeout(0); // see http://contourline.wordpress.com/2011/03/30/preventing-server-timeout-in-node-js/
-}
-function onRequest(request, response) {
- if (request.method === "OPTIONS") {
- response.writeHead(200, {
- "Access-Control-Allow-Origin": "*",
- "Access-Control-Allow-Methods": "POST, GET",
- "Access-Control-Allow-Headers": "Last-Event-ID",
- "Access-Control-Max-Age": "86400"
- });
- response.end();
- return;
+ var headers = {
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ 'Access-Control-Allow-Origin': '*'
+ };
+ if (test === 9) {
+ headers['Access-Control-Allow-Credentials'] = 'true';
+ }
+
+ response.writeHead(200, headers);
+ lastEventId = +request.headers['last-event-id'] || +u.query.lastEventId || 0;
+
+ // 2 kb comment message for XDomainRequest (IE8, IE9)
+ response.write(':' + Array(2049).join(' ') + '\n');
+ response.write('retry: 1000\n');
+ response.write('retryLimit: 60000\n');
+ response.write('heartbeatTimeout: ' + heartbeatTimeout + '\n');//!
+
+ if (!isNaN(test)) {
+ onTest(response, lastEventId, test, cookies);
+ } else {
+ emitter.addListener('message', sendMessages);
+ emitter.setMaxListeners(0);
+ sendMessages();
}
+}
+function onRequest(request, response) {
var url = request.url;
var u = URL.parse(url, true);
var query = u.query;
@@ -220,6 +198,16 @@ function onRequest(request, response) {
}
if (path === '/events') {
+ if (request.method === "OPTIONS") {
+ response.writeHead(200, {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "GET",
+ "Access-Control-Allow-Headers": "Last-Event-ID, Cache-Control",
+ "Access-Control-Max-Age": "86400"
+ });
+ response.end();
+ return;
+ }
eventStream(request, response);
} else {
var files = [

0 comments on commit 22d5f23

Please sign in to comment.