Created
May 17, 2012 04:10
-
-
Save shigeki/2716248 to your computer and use it in GitHub Desktop.
Hello SPDY by Node-0.7.x
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var tls = require('tls'); | |
var fs = require('fs'); | |
var zlib = require('zlib'); | |
var events = require('events'); | |
var util = require('util'); | |
var dictionary = require('./dict.js').dictionary; | |
var word = '<!DOCTYPE html><html><head><title>Hello SPDY</title></head><body>Hello SPDY</body></html>'; | |
function parseNV_HeaderBlock(buffer) { | |
var nv = []; | |
var offset = 0; | |
var num_pairs = buffer.readUInt32BE(offset); | |
offset += 4; | |
for (var i = 0; i < num_pairs; i++) { | |
var nlength = buffer.readUInt32BE(offset); | |
offset += 4; | |
var name = (buffer.slice(offset, offset + nlength)).toString(); | |
offset += nlength; | |
var vlength = buffer.readUInt32BE(offset); | |
offset += 4; | |
var value = (buffer.slice(offset, offset + vlength)).toString(); | |
offset += vlength; | |
nv.push([name, value]); | |
} | |
return nv; | |
} | |
function getNV_HeaderBuffer(headers) { | |
var size = 4; | |
var numOfPair = headers.length; | |
for (var i = 0; i < headers.length; i++) { | |
size += 8 + Buffer.byteLength(headers[i][0]) + Buffer.byteLength(headers[i][1]); | |
} | |
var buf = new Buffer(size); | |
var offset = 0; | |
buf.writeUInt32BE(numOfPair, offset); | |
offset += 4; | |
for (var j = 0; j < headers.length; j++) { | |
var name = headers[j][0]; | |
var nlength = Buffer.byteLength(name); | |
var value = headers[j][1]; | |
var vlength = Buffer.byteLength(value); | |
buf.writeUInt32BE(nlength, offset); | |
offset += 4; | |
buf.write(name, offset); | |
offset += nlength; | |
buf.writeUInt32BE(vlength, offset); | |
offset += 4; | |
buf.write(value, offset); | |
offset += vlength; | |
} | |
return buf; | |
} | |
function SPDYStream(options, listener) { | |
events.EventEmitter.call(this); | |
for (var name in options) { | |
this[name] = options[name]; | |
} | |
if (listener) { | |
this.addListener('inflated', listener); | |
} | |
} | |
util.inherits(SPDYStream, events.EventEmitter); | |
SPDYStream.prototype.parse = function(buf) { | |
if (this.control) { | |
this.version = buf.readUInt16BE(0) & 0x7fff; | |
} else { | |
this.streamid = buf.readUInt32BE(0) & 0x7ffffffff; | |
} | |
this.flags = buf.readUInt8(4); | |
this.length = buf.readUInt32BE(4) & 0x00ffffff; | |
switch (this.type) { | |
case 1: // SYN_STREAM | |
this.streamid = buf.readUInt32BE(8) & 0x7ffffffff; | |
this.astreamid = buf.readUInt32BE(12) & 0x7ffffffff; | |
this.pri = buf.readUInt8(16) >> 6; | |
this.slot = buf.readUInt8(16); | |
this.inflateNV(buf.slice(18)); | |
break; | |
case 2: // SYN_REPLY | |
this.streamid = buf.readUInt32BE(8) & 0x7ffffffff; | |
this.inflateNV(buf.slice(12)); | |
break; | |
} | |
}; | |
SPDYStream.prototype.deflateNV = function(headers, listener) { | |
var buf = getNV_HeaderBuffer(headers); | |
var self = this; | |
var deflate = zlib.createDeflate({dictionary: dictionary}); | |
var data = []; | |
data.size = 0; | |
deflate.on('error', function(e) { | |
console.log('deflateNV Error:', e); | |
}); | |
deflate.on('data', function(d) { | |
data.push(d); | |
data.size += d.length; | |
}); | |
deflate.write(buf); | |
deflate.flush(function() { | |
var nv = new Buffer(data.size); | |
var offset = 0; | |
for (var i = 0; i < data.length; i++) { | |
data[i].copy(nv, offset); | |
offset += data[i].length; | |
} | |
self.nv = nv; | |
listener(); | |
}); | |
}; | |
SPDYStream.prototype.inflateNV = function(buf) { | |
var self = this; | |
var inflate = zlib.createInflate({dictionary: dictionary}); | |
var data = []; | |
data.size = 0; | |
inflate.on('error', function(e) { | |
console.log('inflateNV Error:', e); | |
}); | |
inflate.on('data', function(chunk) { | |
data.push(chunk); | |
data.size += chunk.length; | |
}); | |
inflate.end(buf, function() { | |
var buf = new Buffer(data.size); | |
var offset = 0; | |
for (var i = 0; i < data.length; i++) { | |
data[i].copy(buf, offset); | |
offset += data[i].length; | |
} | |
self.nv = parseNV_HeaderBlock(buf); | |
self.emit('inflated', self); | |
}); | |
}; | |
SPDYStream.prototype.setLength = function() { | |
if (this.control && this.nv) { | |
switch (this.type) { | |
case 1: | |
this.length = this.nv.length + 10; | |
break; | |
case 2: | |
this.length = this.nv.length + 4; | |
break; | |
} | |
} else if (this.data) { | |
this.length = Buffer.byteLength(this.data); | |
} | |
}; | |
SPDYStream.prototype.getFrameBuffer = function() { | |
this.setLength(); | |
var buf = new Buffer(this.length + 8); | |
var offset = 0; | |
var flag = (this.flags << 24) >>> 0; | |
if (this.control) { | |
var control = (0x01 << 15) >>> 0; | |
buf.writeUInt16BE(control | this.version, offset); | |
offset += 2; | |
buf.writeUInt16BE(this.type, offset); | |
offset += 2; | |
buf.writeUInt32BE((this.nv).length + 4, offset); | |
offset += 4; | |
switch (this.type) { | |
case 1: | |
buf.writeUInt32BE(this.streamid, offset); | |
offset += 4; | |
var pri = (this.pri << 13) >>> 0; | |
buf.writeUint16BE(pri | this.slot, offset); | |
offset += 2; | |
(this.nv).copy(buf, offset); | |
break; | |
case 2: | |
buf.writeUInt32BE(this.streamid & 0x7fffffff, offset); | |
offset += 4; | |
(this.nv).copy(buf, offset, 0, (this.nv).length); | |
break; | |
} | |
} else { | |
var streamid = this.streamid & 0x7fffffff; | |
buf.writeUInt32BE(streamid, offset); | |
offset += 4; | |
buf.writeUInt32BE(flag | this.length, offset); | |
offset += 4; | |
buf.write(this.data, offset); | |
} | |
return buf; | |
}; | |
function parseSPDYStream(buf, listener) { | |
var control = (buf.readUInt8(0) & 0x80) === 0x80 ? true : false; | |
var type = buf.readUInt16BE(2); | |
var stream = new SPDYStream({control: control, type: type}, listener); | |
stream.parse(buf); | |
} | |
function getFrameLength(buffer) { | |
return buffer.readUInt32BE(4) & 0x00ffffff; | |
} | |
function getBuffer(buffers) { | |
var buf = new Buffer(buffers.size); | |
var offset = 0; | |
for (var i = 0; i < buffers.length; i++) { | |
buffers[i].copy(buf, offset); | |
offset += buffers[i].length; | |
} | |
return buf; | |
} | |
// send Data Frame to Client | |
function sendData_Frame(socket, syn_stream) { | |
var data = new SPDYStream({control: false, | |
streamid: syn_stream.streamid, | |
flags: 0x01, // FLAG_FIN | |
data: word | |
}); | |
socket.end(data.getFrameBuffer()); // Close socket | |
} | |
// send SYN_REPLY to client | |
function sendSYN_REPLY(socket, syn_stream) { | |
var headers = [[':status', '200 OK'], | |
[':version', 'HTTP/1.1'], | |
['conent-length', word.length + ''], | |
['content-type', 'text/html'], | |
['date', (new Date()).toUTCString()]]; | |
// Create syn_reply object | |
var syn_reply = new SPDYStream({control: true, | |
version: syn_stream.version, | |
type: 0x02, // SYN_REPLY | |
flags: 0x00, | |
streamid: syn_stream.streamid | |
}); | |
// deflate Headers, then send SYN_REPLY to client | |
syn_reply.deflateNV(headers, function() { | |
socket.write(syn_reply.getFrameBuffer(), function() { | |
// send 'Hello SPDY' to client | |
sendData_Frame(socket, syn_stream); | |
}); | |
}); | |
} | |
var options = { | |
key: fs.readFileSync('./foo-key.pem'), | |
cert: fs.readFileSync('./foo-cert.pem'), | |
NPNProtocols: ['spdy/3'] | |
}; | |
var server = tls.createServer(options, function(cleartext) { | |
if (cleartext.npnProtocol !== 'spdy/3') cleartext.end(); | |
var buffers = []; | |
buffers.size = 0; | |
var frame_length = null; | |
cleartext.on('data', function(d) { | |
buffers.push(d); | |
buffers.size += d.length; | |
if (buffers.size < 8) return; | |
if (!frame_length) { | |
frame_length = getFrameLength(getBuffer(buffers)); | |
} | |
if (frame_length && buffers.size === (frame_length + 8)) { | |
parseSPDYStream(getBuffer(buffers), function(syn_stream) { | |
sendSYN_REPLY(cleartext, syn_stream); | |
}); | |
buffers = []; | |
buffers.size = 0; | |
frame_length = null; | |
} | |
}); | |
}); | |
server.listen(3000, function() { | |
console.log('Listening on 3000'); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment