Hello SPDY by Node-0.7.x
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');
});