lib/connection.js
var util = require('util');
var events = require('events');
var Readable = require('stream').Readable;
var h2stream = require('./stream');
var h2hpack = require('hpack');
util.inherits(Http2Reader, Readable);
function Http2Reader(source, options) {
if (!(this instanceof Http2Reader)) {
return new Http2Reader(source, options);
}
Readable.call(this, options);
this._bufArray = [];
this._fsize = -1;
this._curBufLen = 0;
this._source = source;
var self = this;
source.on('end', function() {
self.push(null);
});
source.on('readable', function() {
self.read(0);
});
}
Http2Reader.prototype._read = function(n) {
var chunk = this._source.read();
if (chunk === null) {
return this.push('');
}
this._bufArray.push(chunk);
this._curBufLen += chunk.length;
if (this._bufArray[0].length < 3) {
if (this._bufArray.length === 2) {
this._bufArray = [ Buffer.concat(this._bufArray) ];
} else {
return this.push('');
}
}
do {
if (this._fsize === -1) {
this._fsize = ((this._bufArray[0].readUInt16BE(0) << 8) + this._bufArray[0].readUInt8(2)) + 9;
}
if (this._curBufLen < this._fsize) {
return this.push('');
}
var frameBuf = Buffer.concat(this._bufArray, this._fsize).slice(0, this._fsize);
this._bufArray = [ this._bufArray[this._bufArray.length - 1].slice(this._bufArray[this._bufArray.length - 1].length - (this._curBufLen - this._fsize)) ];
this._curBufLen = this._bufArray[0].length;
this._fsize = -1;
this.emit('frame', frameBuf);
} while (this._bufArray[0].length >= 3);
this.push('');
};
var Connection = module.exports.Connection = function (hostname, port, options) {
var self = this;
this.hostname = hostname;
this.port = port;
this.secure = options.secure;
this.stream = void(0);
this.streams = [];
this._localSettings = [
null, // not used
4096, // SETTINGS_HEADER_TABLE_SIZE
1, // SETTINGS_ENABLE_PUSH
Number.POSITIVE_INFINITY, // SETTINGS_MAX_CONCURRENT_STREAMS
65535, // SETTINGS_INITIAL_WINDOW_SIZE
16384, // SETTINGS_MAX_FRAME_SIZE
Number.POSITIVE_INFINITY // SETTINGS_MAX_HEADER_LIST_SIZE
];
this._remoteSettings = [
null, // not used
4096, // SETTINGS_HEADER_TABLE_SIZE
1, // SETTINGS_ENABLE_PUSH
Number.POSITIVE_INFINITY, // SETTINGS_MAX_CONCURRENT_STREAMS
65535, // SETTINGS_INITIAL_WINDOW_SIZE
16384, // SETTINGS_MAX_FRAME_SIZE
Number.POSITIVE_INFINITY // SETTINGS_MAX_HEADER_LIST_SIZE
];
this.hpack = new h2hpack(this._localSettings[1], this._remoteSettings[1]);
this.highestStreamId = -1;
var connectEvent;
var opt = {
'host': hostname,
'port': port,
};
if (this.secure) {
var tls = require('tls');
opt['rejectUnauthorized'] = false;
opt['ciphers'] = 'TLSv1.2:' + tls.DEFAULT_CIPHERS;
if (options['useNpn']) {
opt['NPNProtocols'] = ['h2', 'h2-14'];
}
this.stream = tls.connect(opt);
connectEvent = 'secureConnect';
} else {
this.stream = require('net').connect(opt);
connectEvent = 'connect';
}
if (options.useUpgrade) {
this.stream.on('readable', function () {
var chunk = this.read();
var response = chunk.toString('ascii');
var end = response.indexOf("\r\n\r\n");
if (end == -1) {
this.unshift(chunk);
return;
} else {
this.removeAllListeners();
this.unshift(chunk.slice(end + 4));
}
self.reader = new Http2Reader(self.stream);
self.reader.on('frame', function (frame) {
self.emit('frame', frame);
});
});
} else {
this.reader = new Http2Reader(this.stream);
this.reader.on('frame', function (frame) {
self.emit('frame', frame);
});
}
this.stream.on(connectEvent, function () {
self.emit('connect', this);
});
this.stream.on('error', function (e) {
self.emit('error', e);
});
this.stream.on('close', function () {
self.emit('close');
});
this.stream.on('end', function (had_error) {
});
};
util.inherits(Connection, events.EventEmitter);
Connection.prototype.send = function (data, callback) {
this.stream.write(data, callback);
};
Connection.prototype.newStream = function (id) {
if (typeof id !== 'undefined') {
if (id % 2 === 1) {
if (id > this.highestStreamId) {
this.highestStreamId = id;
} else {
return id;
}
}
} else {
this.highestStreamId++;
if (this.highestStreamId !== 0 && this.highestStreamId % 2 !== 1) {
this.highestStreamId++;
}
id = this.highestStreamId;
}
var stream = new h2stream.Stream(id, this);
this.streams[id] = stream;
this.emit('newStream', stream);
return id;
};
Connection.prototype.close = function (callback) {
this.stream.end();
if (callback) {
callback();
}
};
Connection.prototype.getLocalSetting = function (id) {
return this._localSettings[id];
};
Connection.prototype.setLocalSetting = function (id, value) {
this._localSettings[id] = value;
};
Connection.prototype.getRemoteSetting = function (id) {
return this._remoteSettings[id];
};
Connection.prototype.setRemoteSetting = function (id, value) {
this._remoteSettings[id] = value;
};