WebSocket ãµã¼ãã®å®è£ ã¨ãããã³ã«è§£èª¬
intro
ãªãã ããã WebSocket ã使ã£ã¦ãã®ã«ã WebSocket ãµã¼ããèªåã§æ¸ãããã¨ãç¡ãã£ãã®ã§ãRFC ãè½ã¡çãã¦ãããããã§ãä»æ§ãèªã¿ãªããå®è£ ãã¦ã¿ããã¨æãã¾ããã
"WebSocket ãµã¼ã å®è£
" ã¨ãã§ã°ã°ãã¨ã Socket.IO ã¨ã pywebsocket 㧠WebSocket ã¢ããªä½ã£ã¦ããWebSocket ãµã¼ããå®è£
ãã¿ãããªã¿ã¤ãã«ã«ãªã£ã¦ããã¨ãå¤ãã¿ããã§ããã
(Apache ã« PHP 㧠HelloWorld ãã¦ããHTTP ãµã¼ãå®è£
ãã¾ãããã¨ã¯è¨ããªãããã)
ãã®è¨äºã§ã¯ã WebSocket ãããã³ã«ãããã¹ããµã¼ãèªä½ãå®è£
ãã¾ãã
ã¨ãã£ã¦ããå
¨é¨ããã®ã¯ã¡ãã£ã¨å¤§å¤ã ã£ãã®ã§ãåºæ¬çãªããã¹ãã¡ãã»ã¼ã¸ã®ããã¨ãã®é¨åã ããã£ã¦ãã¨ã³ã¼ãµã¼ããã§ããã¨ããã¾ã§ããã¾ããã
å®æçã®ã½ã¼ã¹ã¯ä»¥ä¸ã§ããæ¬æãèªã¿ãªãããåããã¦è¦ã¦ããã ããã¨è¯ãã¨æãã¾ãã
ä»æ§
ä»åå®è£
ããã®ã¯ãIETF RFC6455 ã«æºæ ãããµã¼ãã¨æã£ãã®ã§ãããå
¨é¨ããã®ã¯å¤§å¤ã ã£ãã®ã§ããµã¼ãã¨ã¯ã©ã¤ã¢ã³ãã§ããããåç´ãª
ããã¹ããããåãã§ããã¨ããã¾ã§ã«ãã¾ãã
RFC 6455 - The WebSocket Protocol
ã¡ãªã¿ã«ã API ã®æ¹ã¯ W3C ã«ãªãã¾ãã
å ·ä½çã«ã¯ C->S, S->C ãããã 'test' ã¨ããæååãããåããã¾ãã
// Client var ws = new WebSocket("ws://localhost:3000/", ["test", "chat"]); ws.onopen = function() { ws.send("test"); ws.onmessage = function(message) { console.log(message.data); // test }; }
使ã£ãã®ã¯ä»¥ä¸
- Node.js 0.8.3
- Chrome v20
(FireFox ã§ãåããã¿ãã)
HTTP Server
ã¾ããä»å㯠HTTP ãµã¼ãã§é
ä¿¡ãã HTML ã«å«ã¾ãã JS ãããåããµã¼ãã®ä¸ã«ãã WebSocket ãµã¼ãã«ã³ãã¯ã·ã§ã³ãè¦æ±ã確ç«ãã
æãã«ãã¾ãã
ãã®ãããã¾ã㯠Node.js 㧠HTTP ãµã¼ããç«ã¦ãHTML ãé ä¿¡ãã¾ãã
var clientScript = function () { var ws = new WebSocket("ws://localhost:3000/", ["test", "chat"]); // var ws = new WebSocket("ws://localhost:3000/", "test"); ws.onopen = function() { console.log(ws); ws.send("test"); ws.onmessage = function(message) { console.log(message.data); }; } } var server = http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); var html = '<html><head><title>wsserver</title>' + '<script type="text/javascript">' + '(' + clientScript + ')();' + '</script>' + '</head>' + '<body>hello world</body>' + '<html>'; res.end(html); });
ã·ã³ãã«ãª HTML ã®ä¸ã«ãçã® WebSocket ã使ã JS ãåãã¦ãã¾ãã
ãã©ã¦ã¶ã§ WebSocket ãªãã¸ã§ã¯ãã new ããã¨ãå¼æ°ã® WebSocket ãµã¼ãã«æ¥ç¶ãã«ããã¾ãã
ãã®æããã©ã¦ã¶ã¯ HTTP1.1 㧠upgrade ãªã¯ã¨ã¹ããæãã¾ãã
ãã㧠WebSocket ãæå®ãããã¨ã§ãéä¿¡ãããã³ã«ã WebSocket ã« upgrade ããè¦æ±ããã§ããã®ã§ããµã¼ãã¯ãã®ãªã¯ã¨ã¹ããåãåãããããã解æãã¾ãã
HTTP Upgrade Header
upgrade ãªã¯ã¨ã¹ãã®ãããã¯ä»¥ä¸ã®ãããªãã®ã§ãã
1.2. Protocol Overview
ã¾ãã HTTP ã®ãããå¨ãã¯ãã¡ããè¦ãã¨åèã«ãªãã¨æãã¾ãã
studyinghttp.net -
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
- Host
- æ¥ç¶å ã® HOST ã§ãã
- Upgrade
- HTTP1.1 ãå¥ã®ãããã³ã«ã¸åãæ¿ããè¦æ±ã§ãã
- Connection
- ãHTTP/1.1 ã§ã¯ã"Upgrade ã HTTP/1.1 ã¡ãã»ã¼ã¸ã«åå¨ããæã¯å¸¸ã«ãupgrade ã¨ãããã¼ã¯ã¼ãã Connection ããããã£ã¼ã«ã (section 14.10) ã®ä¸ã«ä¸ããªããã°ãªããªã" ã¨ããã¦ããäºã«æ³¨æããããã ããã§ã([http://www.studyinghttp.net/security#SwitchingProtocolFromHTTP:title=link)ã
- Sec-Websocket-Key
- ã¯ã©ã¤ã¢ã³ãã®èªè¨¼ããã»ãã¥ãªãã£ã®ããã«ã¯ã©ã¤ã¢ã³ãã§çæããããã¼ã§ãã
- Origin
- æ¥ç¶ãè¦æ±ãããªãªã¸ã³ã示ãã¾ãã
- Sec-WebSocket-Protocol
- ãµããããã³ã«ã®ãªã¹ãã§ãã
- Sec-WebSocket-Version
- ãããã³ã«ã®ãã¼ã¸ã§ã³ã§ããææ°ã¯13ã
ã§ããããã®è§£æãªãã§ãããå®ã¯ Node.js ã®å ´åã¯å
ç¨ç«ã¦ã HTTP ãµã¼ããåæã«è§£æãã¦ããã¾ãã(ãªãã¨ãããã¼ãw)
ãµã¼ãã« Upgrade ãªã¯ã¨ã¹ããæ¥ãã¨ãServer ãªãã¸ã§ã¯ã㧠upgrade ã¤ãã³ããçºçããã³ã¼ã«ããã¯ã®ç¬¬ä¸å¼æ°ã«è§£æããããããããªãã¸ã§ã¯ãã¨ãã¦æ¸¡ããã¾ãã
server.on('upgrade', function(req, socket, head) { console.log(req); // { host: 'localhost:3000', // upgrade: 'websocket', // connection: 'Upgrade', // origin: 'http://localhost:3000', // 'sec-websocket-protocol': 'test, chat', // 'sec-websocket-extensions': 'x-webkit-deflate-frame', // 'sec-websocket-key': 'NblXHeIwGDpoQ2GFAGzwzw==', // 'sec-websocket-version': '13' } });
ç¾å¨ã Chrome ã§ã¯ Sec-WebSocket-Extensions ããããéããã¦ãã¾ãã
このへんかな
ããããã¯ããã¾ã§æ¡å¼µãªã®ã§ãä»åã¯ç¡è¦ãã¾ãã
gist ã«è²¼ã£ãã½ã¼ã¹ã§ã¯ããã®ãã¨ãããã£ã¦ãç¯å²ã§å¤ããããã§ã®æ¡ä»¶åå²ãªã©ãæ¸ãã¦ãã¾ãããææ°ã®ãããã³ã«ã®ãããã ãã«å¯¾å¿ãããªããã®æç¹ã§ååã¯çµããã§ãw
HTTP Upgrade Response Header
ãªã¯ã¨ã¹ãããã¬ã¹ãã³ã¹ãçæãã¾ãã
ã¬ã¹ãã³ã¹ãããã¯ãã®ããã«ãªãã¾ãã
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat
ã¬ã¹ãã³ã¹ã¯ HTTP1.1 Switching Protocol ãè¿ããã¨ã§ãupgrade ãåãå ¥ãããããã¨ã示ãã¾ãã
ãã®æã Sec-WebSocket-Accept ãã£ã¼ã«ãã¯ããªã¯ã¨ã¹ãã«ãã£ãã Sec-WebSocket-Key ã®å¤ãã以ä¸ã®ããã«ç®åºãããå¤ã使ç¨ãã¾ãã
- Sec-WebSocket-Key(key) ã®æ«å°¾ã®ç©ºç½ãè¦ããå¤ãæºå
- key ã«åºå®å¤ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ãé£çµ
- sha1 ãåå¾
- base64 ã«å¤æ
ã§ãã
Node.js ã§ã¯æ¨æºã® Crypto ã¢ã¸ã¥ã¼ã«ã使ãã¾ãã
key = require('crypto') .createHash('sha1') .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11') .digest('base64');
Sec-WebSocket-Protocol ã¯ãã¯ã©ã¤ã¢ã³ãããæ示ããããµããããã³ã«ããã©ãã(ãããã¯ç¡ã)ãé¸ãã§è¿ãã¾ãã
ãããããµããããã³ã«ã¯ãªãã·ã§ã³ã§å©ç¨ã§ããã¢ããªã±ã¼ã·ã§ã³ã¬ã¤ã¢ã®ãããã³ã«ãªã®ã§ãä»åã¯ãã´ã·ã¨ã¼ã·ã§ã³ãã¦ããã©ã使ãã¾ããã
Data Frame(C->S)
ããããè¿ãããã WebSocket éä¿¡ã確ç«ãã¾ãã
ã¾ãã¯ãã¯ã©ã¤ã¢ã³ãããã¡ãã»ã¼ã¸ãéã£ã¦ã¿ã¾ãããã
ws.send('test');
éãããã¡ãã»ã¼ã¸ã¯ 4byte ã® ASCII æååã§ãã
ãã®æååã¯ãã¤ããªå½¢å¼ã®ãã¼ã¿ãã¬ã¼ã ã§ãµã¼ãã«éããã¾ãã
Node.js ã®å ´åã¯ãã¡ãã»ã¼ã¸ãå±ã㨠'data' ã¤ãã³ãã Socket ãªãã¸ã§ã¯ãã§çºçããã³ã¼ã«ããã¯ã« Buffer ãªãã¸ã§ã¯ãã§ãã®ãã¼ã¿ã渡ãã¾ãã
Buffer ãªãã¸ã§ã¯ãã¯ã Node.js ã§ãªã¯ãããã¹ããªã¼ã (è¦ããã«ãã¤ããªãã¼ã¿ã 8bit ãã¨ã®é åã£ã½ã)æ±ãããã®ãªãã¸ã§ã¯ãã§ãã
ãã®ãã¬ã¼ã ã®å½¢å¼ã¯ä»¥ä¸ã®ããã«ãªã£ã¦ãã¾ãã
RFC 6455 - The WebSocket Protocol
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
ã§ã¯ã Buffer ã®ä¸èº«ãã³ãã³ã解æããªãããåå¤ã解説ãã¦ããã¾ãã
FIN
æå¾ã®ãã±ãããªã 1, ç¶ããªã 0
ä»å㯠1 ('test' ã¨ãããã¼ã¿ã¯ä¸åã§éãããã)
var firstByte = receivedData[0]; /** * fin * axxx xxxx first byte * 1000 0000 mask with 0x80 >>> 7 * --------- * 1 is final frame * 0 is continue after this frame */ var fin = (firstByte & 0x80) >>> 7;
RSV1-3
extention ã使ããªããªã0
ä»åã¯æ¡å¼µã使ããªãã®ã§ç¡è¦ã
opcode
Payload Data ã®èª¬æ
Payload Data ã¨ã¯ãããçãããªãå®ãã¼ã¿(ããã§ã¯ 'test')
- %x0:continuation frame
- %x1:text frame
- %x2:binary frame
- %x3-7:reserved for further
- %x8:connection close
- %x9:ping
- %xA:pong
- %xB-F:reserved for further
ä»å㯠0x1
/** * opcode * xxxx aaaa first byte * 0000 1111 mask with 0x0f * --------- * 0000 0001 is text frame */ var opcode = firstByte & 0x0f;
MASK
1 ãªãPayload Data ããã¹ã¯ããã¦ãããããã¦ããªããã° 0ã
Payload ã¯ãã©ã¦ã¶ãéãã¨ã㯠"å¿
ããã¹ã¯ãã"
ãµã¼ããéãã¨ã㯠"絶対ã«ãã¹ã¯ããªã"
ã¨ãã決ã¾ããããã
ä»ã¯ãã©ã¦ã¶ããã ãã 1
var secondByte = receivedData[1]; /** * mask * axxx xxxx second byte * 1000 0000 mask with 0x80 * --------- * 1000 0000 is masked * 0000 0000 is not masked */ var mask = (secondByte & 0x80) >>> 7; if (mask === 0) { assert.fail('browse should always mask the payload data'); }
Payload Length
Payload Data ã®é·ã
æåã® 7 bit ãèªãã ã¨ã
- 0-125:ãã®ã¾ã¾ããã Payload ã®é·ã
- 126:ããããé·ããããå¾ç¶ã® 16bit ã UInt16 ã¨ã㦠Payload ã®é·ãã表ã
- 127:ãããããé·ããããå¾ç¶ã® 64bit ã UInt64 ã¨ã㦠Payload ã®é·ãã表ã
ããã§ã¯ãå¦çãããã¼ã¿ã¯ 'test' ã¨æ±ºãæã¡ã«ãã¦ã 7bit ã ãèªãå®è£
ã«ããã
ã ããã Pyload Length = 4 ã«ãªãã
/** * Payload Length * xaaa aaaa second byte * 0111 1111 mask with 0x7f * --------- * 0000 0100 4(4) * 0111 1110 126(next UInt16) * 0111 1111 127(next UInt64) */ var payloadLength = (secondByte & 0x7f); if (payloadLength === 0x7e) { assert.fail('next 16bit is length but not supported'); } if (payloadLength === 0x7f) { assert.fail('next 64bit is length but not supported'); }
Masking Key
Payload ããã¹ã¯ãã¦ãããã¼ãã¼ã¿
Payload Length ã®å¾ã«ç¶ãã 32bit ã®ãã¼ã¿ãããã«ãªãã
MASK=1 ã ã£ãå ´åã¯å¿
ãä»ä¸ããããå¾ã§ Payload ãè¤åããã®ã«ä½¿ãã
/** * masking key * 3rd to 6th byte * (total 32bit) */ var maskingKey = receivedData.readUInt32BE(2);
Payload Data
å®ãã¼ã¿ã®é¨åãå®é㯠Extention Data + Application Data
以éãæ«å°¾ã¾ã§ã®ãã¼ã¿ã Payload Data ã«ãªã£ã¦ããã
ããããå®éãã㯠Extention Data + Application Data ã¨ãªã£ã¦ããã
Extention Data ã¨ã¯ããã´ã·ã¨ã¼ã·ã§ã³ã®éç¨ã§ã Extention ã使ãã¨æ±ºãã
å ´åã«ä»ä¸ããããã¼ã¿ã ãããä»åã¯ä½¿ããªãã(ã¨ããã Payload ã«å«ã¾ããªã)
Application Data ã¯ä»åã§è¨ã 'test' ã®ãã¨ã
ã ããã Payload Data == Application Data ã¨èãã¦ããã
Payload Length ã 4byte ã¨ããã£ã¦ããããã
32bit ã Big Endian ã§èªãã§ãããã°ãã(TCP=BigEndian ã ããã ã¨æã£ã¦ããã©ãã£ã¦ãã®ããªï¼)
var applicationData = receivedData.readUInt32BE(6);
èªã¿ã ãããã¼ã¿ããå
ã»ã©ã® Masking Key 㧠unmask ããã
(Masking Key ã¨ã® XOR ãã¨ã£ã¦ãããã°ãã)
var unmasked = applicationData ^ maskingKey;
ãã®å¤ã UTF-8 ã§ã¨ã³ã³ã¼ããã¦ãããã°ãããã ãã©ã
ä»ã®æç¹ã§ã¯ Buffer ãããªããããä¸æ¦ãããã¡ã«ãã¦ããã
Buffer.toString() ãããã (ãã£ã¨ããæ¹æ³ãããã)
Buffer Node.js v0.8.26 Manual & Documentation
var unmaskedBuf = new Buffer(4); unmaskedBuf.writeInt32BE(unmasked, 0); var encoded = unmaskedBuf.toString(); console.log(encoded)' // test
ããã§ç¡äºãã¯ã©ã¤ã¢ã³ããæããå¤ãåãåºãã¾ããã
Data Frame(S->C)
次ã¯ã¯ã©ã¤ã¢ã³ãã«ãã¼ã¿ 'test' ãéãã¾ãã
ã¨ãã£ã¦ãããã£ãã®éãããã°ããã ãã§ãã
大ããªéãã¯ã
ããµã¼ãããã¯ã©ã¤ã¢ã³ãã«éãå ´åã¯ããã¹ã¯ããªãã
ã¨ããç¹ã§ãã
åå¤ã¯ä»¥ä¸ã®ããã«ãªãã¾ãã
- FIN:1
- OPCODE: 1
- MASK: 0
- Payload Length: 4
- Payload: 'test'
å¤æãæãæãã¦ãæ®éã«æ¸ãè¾¼ãã ããããªæãã
ä¸æ¦ Buffer ãªãã¸ã§ã¯ãã«è²¯ãã¦ã socket.end() ã«æ¸¡ãã¨ãã¯ã©ã¤ã¢ã³ãã«éã£ã¦ããã¾ãã
String ãã㯠charCodeAt() ã使ãã¾ãã
/** * Sending data to client * data must not mask */ var sendData = new Buffer(6); // FIN:1, opcode:1 // 0x81 = 10000001 sendData[0] = 0x81; // MASK:0, len:4 // 0x4 = 100 sendData[1] = 0x4; // payload data // send data "test" sendData[2] = 'test'.charCodeAt(0); sendData[3] = 'test'.charCodeAt(1); sendData[4] = 'test'.charCodeAt(2); sendData[5] = 'test'.charCodeAt(3); // send to client socket.end(sendData);
ãã¾ãããã°ãã¯ã©ã¤ã¢ã³ãã§ã¯ onmessage ã¤ãã³ããçºçããdata ãã£ã¼ã«ãã«éã£ããã¼ã¿ãæ ¼ç´ããã¾ãã
ws.onmessage = function(message) { console.log(message.data); // test };
ã¾ã¨ã
WebSocket ã®ä»æ§èªä½ã¯ãå ¨é¨å®è£ ãããã¨ããã¨ã¡ãã£ã¨å¤§å¤ã ãã©ãä»ã¯ã¾ã èªããã¨æãã°èªããç¨åº¦ã®éã ã¨æãã¾ãã
ãã大ããªåé¡ã¯ãããã¾ã§ã«çã¾ãã¦ã¯æ¶ãããããã³ã«ã®å®è£ ãæã£ããå¤ããã©ã¦ã¶ã¨ã®äºææ§ã§ãããããããèããªããã°ãé å¼µãã°å®è£ ã§ããªãã¯ãªãããã
ããããç¾å¨ã® RFC ã«ãã¦ããã¾ã ã¾ã ä»æ§ãå¤ããå¯è½æ§ã¯ãç¡ãã¯ãªãããéä¸ã§åºã¦ãã Extention ã Subprotocol, ã¾ãå¥ã§é²ãã§ã multiplexing ãªã©ã«ãã£ã¦ãä»å¾ããå°ãå¤ããå¯è½æ§ãããã¾ãã
WebSocket ãã¢ããªã§ä½¿ãå ´åã¯ããã¨ãªããã¡ã³ããã³ã¹ããã¦ããå®è£
ã使ãã®ãåã§ããããããã¦èªåã§ã¡ãã£ã¨ã§ãå®è£
ããã¨å¾ããããã®ãå¤ãã®ã§ãå§ãã§ãã
JS ã¯ãã¨ãã¨ãã¤ããªãå¦çããæåãç¡ãã£ããããNode.js ã¯ç¬èªã« Buffer ã¢ã¸ã¥ã¼ã«ãä½ã£ã¦ãã¾ãããJavaScript ã«ã¯ TypedArray ã¨ããä»æ§ãæè¿åºã¦ãã¦ããã®ã§ãããã Node ããã£ã¡ã使ããã¨ã«ãªãã¨æãã¾ãã
ãããããããä»æ§ã®èªã¿éããªã©ããããããã¾ãããææã質åãªã©ãã£ã¼ãããã¯æè¿ã§ãã