nanha
7/4/2012 - 2:59 AM

Hello SPDY by Node-0.7.x

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');
});