SSEã®æŠå¿µã¯åçŽã§ããã¯ã©ã€ã¢ã³ãã¯ãµãŒããŒã€ãã³ãã«ãµãã¹ã¯ã©ã€ãããã€ãã³ããçºçãããšããã«ãã¯ã©ã€ã¢ã³ãã¯éç¥ãšãã®ã€ãã³ãã«é¢é£ããããŒã¿ãããã«åä¿¡ããŸãã SSEãããã³ã«ã®æçšæ§ãç解ããã«ã¯ãã€ãã³ããåä¿¡ããããã®éåžžã®æ¹æ³ãšæ¯èŒããå¿ èŠããããŸãããã®æ¬è³ªãç°¡åã«èª¬æããŸãã
ããŒãªã³ã°
æãåçŽã§ãããæãå¹æçãªæ¹æ³ã§ã¯ãããŸãããã¯ã©ã€ã¢ã³ãã¯æ°ç§ããšã«ã€ãã³ããæ±ããŠãµãŒããŒãããŒãªã³ã°ããŸãã äœãååšããªãå Žåã§ããã¯ã©ã€ã¢ã³ãã¯ãã¹ãŠåãããã«èŠæ±ãè¡ããŸãããäœãæ¥ããããããŸããã
é·æïŒ
-ãã
-ããŒã¿ãæ¯ãããšãã§ããŸã
çæïŒ
-å€ãã®è¿œå ãªã¯ãšã¹ã
-ã€ãã³ãã¯åžžã«é ãã
-ãµãŒããŒã¯ãã¯ã©ã€ã¢ã³ããã€ãã³ããååŸããããã€ãã³ããæéåãã«ãªããŸã§ã€ãã³ããä¿åããå¿ èŠããããŸã
é·ãããŒãªã³ã°
以åã®æ¹æ³ã®æ¹è¯çã ã¯ã©ã€ã¢ã³ãã¯ãµãŒããŒã«ãªã¯ãšã¹ããéä¿¡ãããµãŒããŒã¯ããŒã¿ãå°çãããã¯ã©ã€ã¢ã³ããèªåçã«åæãããŸã§æ¥ç¶ãéãããŸãŸã«ããŸãã ããŒã¿ãå°çãããšããã«ãå¿çãéä¿¡ãããæ¥ç¶ãéãããã次ã®æ¥ç¶ãéãããŸãã
ããŒãªã³ã°ãšæ¯èŒããé·æïŒ
-ãªã¯ãšã¹ãã®æå°æ°
-ã€ãã³ãã®é«ãæéç粟床
-ãµãŒããŒã¯åæ¥ç¶äžã«ã®ã¿ã€ãã³ããä¿åããŸã
ããŒãªã³ã°ãšæ¯èŒããçæïŒ
-ããè€éãªã¹ããŒã
Websocket
ããã¯ãã¯ã©ã€ã¢ã³ããšãµãŒããŒãåçã®æ¡ä»¶ã§éä¿¡ã§ããããã«ãããã€ããªäºéãããã³ã«ã§ãã ãã®ãããã³ã«ã¯ãã²ãŒã ããã£ãããããã³ãªã¢ã«ã¿ã€ã ã«è¿ãéåžžã«æ£ç¢ºãªã€ãã³ããå¿ èŠãªãã¹ãŠã®ã¢ããªã±ãŒã·ã§ã³ã«äœ¿çšã§ããŸãã
ãã³ã°ããŒãªã³ã°ãšæ¯èŒããé·æïŒ
-1ã€ã®æ¥ç¶ãäžæãã
-ã€ãã³ãã®éåžžã«é«ãæéç粟床
-ãã©ãŠã¶ãŒã«ãã£ãŠå¶åŸ¡ããããããã¯ãŒã¯é害管ç
ãã³ã°ããŒãªã³ã°ãšæ¯èŒããçæïŒ
-HTTPã¯äºææ§ã®ãããããã³ã«ã§ã¯ãªããããç¬èªã®ãµãŒããŒãå¿ èŠã§ãããããã°ã¯è€éã§ã
ç§ãã¡ã¯ãã®ãããªçŸããWebSocketãããã³ã«ãæã£ãŠããã®ã§ããªãSSEã䜿çšããå¿ èŠããããŸããïŒïŒ ãŸãããã¹ãŠã®Webã¢ããªã±ãŒã·ã§ã³ãåæ¹åéä¿¡ãå¿ èŠãšããããã§ã¯ãããŸãã-SSEãé©ããŠããŸãã 次ã«ãSSEã¯HTTPäºæãããã³ã«ã§ãããä»»æã®WebãµãŒããŒã§ã€ãã³ããããŒããã£ã¹ããå®è£ ã§ããŸãã
ãµãŒããŒéä¿¡ã€ãã³ããããã³ã«
ã¯ã©ã€ã¢ã³ãã¯ãµãŒããŒã«èŠæ±ãéä¿¡ãããµãŒããŒã¯å¿çãšããŠæ¬¡ã®ããããŒãéä¿¡ããŸãã
Content-Type: text/event-stream
ãŸããæ¥ç¶ãéããŸããïŒphpã§ã¯ãç¡éã«ãŒããäœæã§ããŸããnode.jsã§è¡ãæ¹æ³ã«ã€ããŠã¯ããµã³ãã«èšäºã§èª¬æããŸãïŒã ããã ãã§ã-SSEã¯åäœããŸãïŒ ã¯ã©ã€ã¢ã³ãã«ããŒã¿ãéä¿¡ããããã«ããµãŒããŒã¯æ¬¡ã®åœ¢åŒã®è¡ããœã±ããã«æžã蟌ãã ãã§ãã
data: My message\n\n
è€æ°è¡ã®ããŒã¿ãéä¿¡ããå¿ èŠãããå Žåã圢åŒã¯æ¬¡ã®ããã«ãªããŸãã
data: {\n data: "msg": "hello world",\n data: "id": 12345\n data: }\n\n
ããã§ã¯ãååãšããŠããããã³ã«ããŒã¹å šäœã§ãã ããã«ããµãŒããŒã¯ã¡ãã»ãŒãžIDãéä¿¡ã§ããŸããããã¯ãæ¥ç¶ãåæãããå Žåã«å¿ èŠã§ãã æ¥ç¶ãåæãããå Žåãã¯ã©ã€ã¢ã³ãã¯æ¥ç¶ããããšãããšãç¹å¥ãªããããŒïŒLast-Event-IDïŒãéä¿¡ããŠã倱ãããã€ãã³ãã埩å ããŸãã
id: 12345\n data: GOOG\n data: 556\n\n
ãšã©ãŒãçºçããå Žåã®åè©Šè¡æéïŒ
retry: 10000\n data: hello world\n\n
idããã³retryãã£ãŒã«ãã¯ãªãã·ã§ã³ã§ãã
ã¯ã©ã€ã¢ã³ãã§ã¯ããã¹ãŠã次ã®ããã«ãªããŸãã
var source = new EventSource('http://localhost/stream.php'); source.addEventListener('message', function(e) { // - console.log(e.data); }, false); source.addEventListener('open', function(e) { // }, false); source.addEventListener('error', function(e) { if (e.eventPhase == EventSource.CLOSED) { // } }, false);
ãã¹ãŠãéåžžã«ç°¡åã§ãã SSEãããã³ã«ã«åºã¥ããŠã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããŸãããã ãã€ãã®ããã«ãããã¯ãã£ããã«ãªããŸãã
ãã«ãããŒãXMLHTTPRequest
ãã«ãããŒãã¹ããªãŒãã³ã°ãšãåŒã°ããŸãïŒFirefoxã®ã¿ããµããŒãïŒã SSEãããã³ã«ã«éåžžã«äŒŒãŠããŸãã
ããããŒã®åœ¢åŒã¯æ¬¡ã®ãšããã§ãã
Content-type: multipart/x-mixed-replace;boundary=smthing
ãããŠãããŒãã¯æ¬¡ã®åœ¢åŒã§éä¿¡ãããŸãã
Content-type: text/html\r\n\r\n --smthing\n Message\n --smthing\n
éåžžã®XHRãã¯ã©ã€ã¢ã³ãã§äœæãããŸããããªã¯ãšã¹ããéä¿¡ããåã«ãã©ã°
req.multipart = true;
èšå®ããå¿ èŠããã
req.multipart = true;
SSEã®ããã«èŠããŸããïŒ è©³çŽ°
SSEã«ã€ãªããå¯èœæ§ã®ããå¥ã®ãããã³ã«ããããŸãã
XMLHTTPRequestïŒã€ã³ã¿ã©ã¯ãã£ã
ããã䜿çšããã«ã¯ããã©ãŠã¶ã¯ã³ãŒã3ïŒã€ã³ã¿ã©ã¯ãã£ãïŒã§ç¹å¥ãªreadyStateããµããŒãããå¿ èŠããããŸã-ãã®ã¹ããŒã¿ã¹ã¯ãããŒã¿ã®äžéšãå°çããããæ¥ç¶ããŸã éããããŠããªãããšã瀺ããŸãã ã³ãŒã3ã§readyStateã䜿çšããjQuery ã«ã¯åãååã®ãã©ã°ã€ã³ããããŸãããŸãããã€ãã®ããã«ããã¹ãŠã®ãã©ãŠã¶ãŒãã³ãŒã3ã§readyStateããµããŒãããŠããããã§ã¯ãããŸããã
äŸïŒãµãŒããŒéä¿¡ã€ãã³ãã§ã®ãã£ãã
SSEã«é¢ããäžé£ã®ã€ãã³ãïŒãªãã©ã€ã³ã«ãªãããªã³ã©ã€ã³ã«ãªããã¡ãã»ãŒãžïŒãåãå ¥ããŸãã ãªããªã SSEã¯ã¡ãã»ãŒãžãéä¿¡ã§ããªããããHTTPãä»ããŠéä¿¡ããŸãã
äœæ¥ã®ã¹ããŒã ã¯æ¬¡ã®ãšããã§ãã
-ãã£ããã«å ¥ããšããååãèŠæ±ãããŸã
-ã¯ã©ã€ã¢ã³ãã¯ãã£ãããµãŒããŒã«æ¥ç¶ããŸãã ã€ãã³ãã¹ããªãŒã ãäœæãããŸãã
-ã¯ã©ã€ã¢ã³ããæ¥ç¶ãããšããã£ããã¯å šå¡ã«ã€ãã³ããéä¿¡ããŸãïŒïŒ usernameïŒ online
-ã¯ã©ã€ã¢ã³ããåæãããšããã£ããã¯å šå¡ã«ã€ãã³ããéä¿¡ããŸãïŒïŒ usernameïŒ offline
-ã¯ã©ã€ã¢ã³ãã¯HTTPãPOST /ã¡ãã»ãŒãžããä»ããŠã¡ãã»ãŒãžãéä¿¡ã§ãããµãŒããŒã¯ãã®ã¡ãã»ãŒãžãåä¿¡ããSSEãä»ããŠãã¹ãŠã®ã¯ã©ã€ã¢ã³ãã«åä¿¡ããã¡ãã»ãŒãžãéä¿¡ããŸãã
ã¯ã©ã€ã¢ã³ãã³ãŒããåæããŸãããã äžéšã®ãã©ãŠã¶ã§$ .readyã®ä»£ããã«ç¡éã®ããŠã³ããŒããè¡ãããªãããã«ãsetTimeoutãå®è¡ããŸãã
setTimeout(function () { // , $.ready }, 50);
ãŠãŒã¶ãŒåããªã¯ãšã¹ãïŒ
// localStorage var name = (prompt('Name:', window.localStorage ? window.localStorage['name'] || '' : '') || 'anonymous').substr(0, 20); // if (window.localStorage) { window.localStorage['name'] = name; }
EventSourceãäœæããããã«ãŠãŒã¶ãŒåãæž¡ãïŒãŠãŒã¶ãŒããªã³ã©ã€ã³ã«ãªã£ãïŒãå¿ èŠãªã€ãã³ãããªãã¹ã³ããŸãã
var eventSrc = new EventSource("/event?name=" + name); // EventSource - "message" eventSrc.addEventListener("message", function(event) { var data = JSON.parse(event.data); // renderMessage(data); }, false); // EventSource - "error" eventSrc.addEventListener("error", function(event) { // renderMessage({ isbot: true, message: 'connection error', name: '@Chat' }); }, false);
renderMessageã¡ãœãããšããŒãžã¬ã€ã¢ãŠãã¯èæ ®ããŸããã ãã¹ãŠã®ã¯ã©ã€ã¢ã³ãã³ãŒãã¯ã index.htmlã«ãããŸãã
ãµãŒããŒåŽã«ã¯ãNode.jsããããŸãã ããã§ã¯ãã¹ãŠãããè€éã§ããã1人ã®ãŠãŒã¶ãŒãããã¹ãŠã®ãŠãŒã¶ãŒãžã®ãã«ããã£ã¹ãã¡ãã»ãŒãžã®äž»ãªé£ããã§ãããSSEãä»ããéä¿¡ã®æ§ç¯ã¯å°é£ã§ãã
å¿ èŠãªã¢ãžã¥ãŒã«ãæ¥ç¶ããŸã
var http = require('http'), fs = require('fs'), qs = require('querystring'), parse = require('url').parse; // (index.html Node.js) var indexFile = fs.readFileSync('index.html'); // Buffer
ã«ãŒã
次ã®ãªããžã§ã¯ããå«ãã«ãŒãã«ãŒãã®ãªã¹ããäœæããŸãã
1.éååŠã ã€ã³ããã¯ã¹ããŒãžãéçããŒã¿ã®ãã«ã¡ããïŒ
'GET /': function (request, response) { // response.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'}); response.write(indexFile); response.end(); }
2. SSEæ¥ç¶ã®ç¢ºç«ïŒ
'GET /event': function (request, response) { var url = parse(request.url, true); var name = (url.query.name || 'anonymous').substr(0, 20); var clientId = Clients.generateClientId(); // EventSource response.writeHead(200, {'Content-Type': 'text/event-stream'}); // , 2 request.socket.setTimeout(1000 * 60 * 60); // 1 // - request.on('close', function () { Clients.remove(clientId); }); // Clients.add(clientId, response, name); }
3.ã¯ã©ã€ã¢ã³ãããã®ã¡ãã»ãŒãžïŒ
'POST /message': function (request, response) { var data = ''; // POST request.on('data', function (chunk) { data += chunk; }); // POST request.on('end', function () { // data = qs.parse(data); // Clients.broadcast(data.message, data.name, false); response.writeHead(200); response.end(); }); }
4.ããã©ã«ãã«ãŒã-ããŒãž404ïŒ
$: function (request, response) { response.writeHead(404); response.end(); }
ã«ã¹ã¿ããŒãããŒãžã£ãŒ-ã¯ã©ã€ã¢ã³ã
æ°ããã¯ã©ã€ã¢ã³ããè¿œå ïŒè¿œå ïŒãããšããããŒãžã£ãŒã¯ã¯ã©ã€ã¢ã³ããå°çãããã¹ãŠã®ã¡ãã»ãŒãžãéä¿¡ããŸãã
add: function (clientId, response, name) { this._clients[clientId] = {response: response, name: name || 'anonymous'}; this.count++; // this.unicast(clientId, 'Hello, ' + name + '! Online ' + this.count, '@ChatBot', true); this.broadcast(name + ' online', '@ChatBot', true); }
åé€ãããšãæ¥ç¶ãéããããã¯ã©ã€ã¢ã³ãããªãã©ã€ã³ã§ããããšãå šå¡ã«éä¿¡ããŸãã
remove: function (clientId) { // , var client = this._clients[clientId]; if (!client) { return; } // client.response.end(); // delete this._clients[clientId]; this.count--; // , // this.broadcast(client.name + ' offline', '@ChatBot', true); }
private _sendã¡ãœããã¯ãã¯ã©ã€ã¢ã³ãã«ã¡ãã»ãŒãžãéä¿¡ããããã«äœ¿çšãããŸãã
_send: function (clients, message, name, isbot) { if (!message || !name) { return; } // var data = JSON.stringify({ message: message.substr(0, 1000), name: (name || 'anonymous').substr(0, 20), isbot: isbot || false }); // , // - Node.js data = new Buffer("data: " + data + "\n\n", 'utf8'); // SSE // clients.forEach(function (client) { client.response.write(data); // }); }
_sendã¡ãœããã¯ããããªãã¯ãããŒããã£ã¹ãã¡ãœãããšãŠããã£ã¹ãã¡ãœããã䜿çšããŠããããããã¹ãŠã®ã¯ã©ã€ã¢ã³ããš1ã€ã®ã¯ã©ã€ã¢ã³ãã«ã¡ãã»ãŒãžãéä¿¡ããŸãã
ãµãŒããŒãäœæããŠãªã³ã«ããŸã
// var httpServer = http.createServer(function (request, response) { var key = request.method + ' ' + parse(request.url).pathname; // , Routes.$ - 404 (Routes[key] || Routes.$)(request, response); }); // httpServer.listen(80); console.log('Online');
ãœãŒã¹ã³ãŒãserver.js
SSEã§ã®ãã£ããã®æºåãã§ããŸããã ãµãŒããŒãèµ·åããŸãã
$ node server.js
Firefox 6ãOpera 10.6以éãChromeãWebKit 5以éãiOS Safari 4以éãOpera Mobile 10以éã®ããããã®ãã©ãŠã¶ãŒãéããŸãã
http://
localhost/
ã«ã¢ã¯ã»ã¹ããŠãã£ããããŠãã ããïŒ
ãããã«
SSEã¯Long Polingã«åã£ãŠä»£ããåªãããã¯ãããžãŒã§ãããã·ã³ãã«ã§ãããWebSocketsã»ã©å¹æçã§ã¯ãããŸããã çŸåšãSSEã¯Opera 10.6+ïŒOpera 9ã¯å€ãSSEæšæºããµããŒãããŠããŸãïŒãChromeãSafari 5+ããµããŒãããŠããŸãã Firefoxã¯Multipart XMLHTTPRequestããµããŒãããŠããŸãããã®ããã«ã©ãããŒãäœæããSSEã€ã³ã¿ãŒãã§ãŒã¹ãšããŠäœ¿çšã§ããŸãã
åç §è³æ
1. SSEãã£ããã®ãªã³ã©ã€ã³äŸã¯ã sse-chat.nodester.comã§èŠãããšãã§ããŸãã
Nodesterãããã·ã®æ©èœã«ãããããã¯ãã£ããã®äžéšãåãæšãŠãããããŒãžã§ã³ã§ãïŒãªã³ã©ã€ã³ã®ãŠãŒã¶ãŒæ°ã«é¢ããã¡ãã»ãŒãžã¯ãªãããã£ããããéåºãããšããã¡ãã»ãŒãžããããŸãããé »ç¹ã«åæ¥ç¶ãããå¯èœæ§ããããŸãïŒ
2. ãœãŒã¹ã®äŸïŒ github.com/azproduction/event-source-chat
3. å¥ã®SSEãã¥ãŒããªã¢ã«
4. ä»æ§
PSãã£ããã¯habraeffectãã«ããŒããŠããããã«èŠããŸãããnodesterã§äœããããŠããããã§ãïŒå€ãã®å Žåããã®ããã«ãªããŸãïŒã çµæã«èå³ãããå Žåã¯ãGitHubãããœãŒã¹ãããŠã³ããŒãããŠãã ããã
ãã«ãããŒãXMLHTTPRequestãXMLHTTPRequestã«ããUPDãè¿œå ãããŸããïŒ yui_room9ãè¿œå ããŠãããã€ã³ã¿ã©ã¯ãã£ããªæè¬