Socket
Socket
Sign inDemoInstall

http2

Package Overview
Dependencies
Maintainers
1
Versions
44
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

http2 - npm Package Compare versions

Comparing version 0.4.0 to 0.4.1

13

example/client.js

@@ -5,12 +5,5 @@ var fs = require('fs');

if (process.env.HTTP2_LOG) {
http2.globalAgent = new http2.Agent({
log: require('bunyan').createLogger({
name: 'client',
stream: process.stderr,
level: process.env.HTTP2_LOG,
serializers: http2.serializers
})
});
}
http2.globalAgent = new http2.Agent({
log: require('../test/util').createLogger('client')
});

@@ -17,0 +10,0 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

@@ -5,10 +5,3 @@ var fs = require('fs');

if (process.env.HTTP2_LOG) {
var log = require('bunyan').createLogger({
name: 'server',
stream: process.stderr,
level: process.env.HTTP2_LOG,
serializers: http2.serializers
});
}
var serverjs = fs.readFileSync(path.join(__dirname, './server.js'));

@@ -18,3 +11,3 @@ var options = {

cert: fs.readFileSync(path.join(__dirname, '/localhost.crt')),
log: log
log: require('../test/util').createLogger('server')
};

@@ -25,4 +18,9 @@

if ((filename.indexOf(__dirname) === 0) && fs.existsSync(filename) && fs.statSync(filename).isFile()) {
var filestream = fs.createReadStream(filename);
// Serving server.js from cache. Useful for microbenchmarks.
if (request.url === '/server.js') {
response.end(serverjs);
}
// Reading file from disk if it exists and is safe.
else if ((filename.indexOf(__dirname) === 0) && fs.existsSync(filename) && fs.statSync(filename).isFile()) {
response.writeHead('200');

@@ -37,5 +35,7 @@

filestream.pipe(response);
fs.createReadStream(filename).pipe(response);
}
} else {
// Otherwise responding with 404.
else {
response.writeHead('404');

@@ -42,0 +42,0 @@ response.end();

Version history
===============
### 0.4.1 (2013-09-15) ###
* Major performance improvements
* Minor improvements to error handling
* [Blog post](http://gabor.molnar.es/blog/2013/09/15/gsoc-week-number-13/)
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.4.1.tar.gz)
### 0.4.0 (2013-09-09) ###

@@ -5,0 +12,0 @@

@@ -25,3 +25,3 @@ // The implementation of the [HTTP/2 Header Compression][http2-compression] spec is separated from

var TransformStream = require('stream').Transform;
var assert = process.env.HTTP2_ASSERT ? require('assert') : function noop() {};
var assert = require('assert');
var util = require('util');

@@ -58,2 +58,17 @@

// [referenceset]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-03#section-3.1.3
//
// Relations of the sets:
//
// ,----------------------------------.
// | Header Table |
// | |
// | ,----------------------------. |
// | | Reference Set | |
// | | | |
// | | ,---------. ,---------. | |
// | | | Keep | | Emitted | | |
// | | | | | | | |
// | | `---------' `---------' | |
// | `----------------------------' |
// `----------------------------------'
function entryFromPair(pair) {

@@ -79,3 +94,3 @@ var entry = pair.slice();

function size(entry) {
return new Buffer(entry[0] + entry[1], 'utf8').length + 32;
return (new Buffer(entry[0] + entry[1], 'utf8')).length + 32;
}

@@ -375,3 +390,24 @@

// * if there's full match, it will be an indexed representation (or more than one) depending
// on its presence in the reference, the emitted and the keep set
// on its presence in the reference, the emitted and the keep set:
//
// * If the entry is outside the reference set, then a single indexed representation puts the
// entry into it and emits the header.
//
// * If it's already in the keep set, then 4 indexed representations are needed:
//
// 1. removes it from the reference set
// 2. puts it back in the reference set and emits the header once
// 3. removes it again
// 4. puts it back and emits it again for the second time
//
// It won't be emitted at the end of the decoding process since it's now in the emitted set.
//
// * If it's in the emitted set, then 2 indexed representations are needed:
//
// 1. removes it from the reference set
// 2. puts it back in the reference set and emits the header once
//
// * If it's in the reference set, but outside the keep set and the emitted set, then this
// header is common with the previous header set, and is still untouched. We mark it to keep
// in the reference set (that means don't remove at the end of the encoding process).
if (fullMatch !== -1) {

@@ -771,3 +807,3 @@ rep = { name: fullMatch, value: fullMatch, index: -1 };

if (chunkFrame.type !== 'PUSH_PROMISE') {
chunkFrame.flags.END_STREAM = last && frame.END_STREAM;
chunkFrame.flags.END_STREAM = last && frame.flags.END_STREAM;
}

@@ -774,0 +810,0 @@ chunkFrame.data = chunks[i];

@@ -1,2 +0,2 @@

var assert = process.env.HTTP2_ASSERT ? require('assert') : function noop() {};
var assert = require('assert');

@@ -19,4 +19,7 @@ // The Connection class

//
// * **Event: 'error' (type)**: signals a connection level error
// * **Event: 'error' (type)**: signals a connection level error made by the other end
//
// * **Event: 'peerError' (type)**: signals the receipt of a GOAWAY frame that contains an error
// code other than NO_ERROR
//
// * **Event: 'stream' (stream)**: signals that there's an incoming stream

@@ -29,3 +32,3 @@ //

//
// * **ping(callback)**: send a ping and call callback when the answer arrives
// * **ping([callback])**: send a ping and call callback when the answer arrives
//

@@ -58,2 +61,3 @@ // * **close([error])**: close the stream with an error code

// * multiplexing
this._initializeMultiplexing();
}

@@ -115,3 +119,3 @@ Connection.prototype = Object.create(Flow.prototype, { constructor: { value: Connection } });

// be set by the SETTINGS_MAX_CONCURRENT_STREAMS setting.
this._streamCount = 0;
this._streamSlotsFree = Infinity;
this._streamLimit = Infinity;

@@ -134,6 +138,10 @@ this.on('RECEIVING_SETTINGS_MAX_CONCURRENT_STREAMS', this._updateStreamLimit);

// Method to manage the stream slot pool:
// Methods to manage the stream slot pool:
Connection.prototype._updateStreamLimit = function _updateStreamLimit(newStreamLimit) {
var wakeup = (this._streamSlotsFree === 0) && (newStreamLimit > this._streamLimit);
this._streamSlotsFree += newStreamLimit - this._streamLimit;
this._streamLimit = newStreamLimit;
this.read(0);
if (wakeup) {
this.emit('wakeup');
}
};

@@ -143,5 +151,9 @@

if (change) {
this._log.trace({ count: this._streamCount, change: change }, 'Changing active stream count.');
this._streamCount += change;
this.read(0);
this._log.trace({ free: this._streamSlotsFree, change: change },
'Changing active stream count.');
var wakeup = (this._streamSlotsFree === 0) && (change < 0);
this._streamSlotsFree -= change;
if (wakeup) {
this.emit('wakeup');
}
}

@@ -181,6 +193,10 @@ };

// * adding to `this._streamIds`
this._log.trace({ s: stream, stream_id: id }, 'Allocation ID for stream.');
this._log.trace({ s: stream, stream_id: id }, 'Allocating ID for stream.');
this._streamIds[id] = stream;
stream.id = id;
this.emit('new_stream', stream, id);
// * handling stream errors as connection errors
stream.on('error', this.emit.bind(this, 'error'));
return id;

@@ -191,7 +207,7 @@ };

Connection.prototype._allocatePriority = function _allocatePriority(stream) {
this._log.trace({ s: stream }, 'Allocation priority for stream.');
this._log.trace({ s: stream }, 'Allocating priority for stream.');
this._insert(stream, stream._priority);
stream.on('priority', this._reprioritize.bind(this, stream));
stream.upstream.on('readable', this.read.bind(this, 0));
this.read(0);
stream.upstream.on('readable', this.emit.bind(this, 'wakeup'));
this.emit('wakeup');
};

@@ -246,5 +262,22 @@

Connection.prototype._initializeMultiplexing = function _initializeMultiplexing() {
this.on('window_update', this.emit.bind(this, 'wakeup'));
this._sendScheduled = false;
this._firstFrameReceived = false;
};
// The `_send` method is a virtual method of the [Flow class](flow.html) that has to be implemented
// by child classes. It reads frames from streams and pushes them to the output buffer.
Connection.prototype._send = function _send() {
Connection.prototype._send = function _send(immediate) {
// * Collapsing multiple calls in a turn into a single deferred call
if (immediate) {
this._sendScheduled = false;
} else {
if (!this._sendScheduled) {
this._sendScheduled = true;
setImmediate(this._send.bind(this, true));
}
return;
}
this._log.trace('Starting forwarding frames from streams.');

@@ -255,34 +288,32 @@

for (var priority in this._streamPriorities) {
var bucket = this._streamPriorities[priority].slice();
var bucket = this._streamPriorities[priority];
var nextBucket = [];
// * Forwarding frames from buckets with round-robin scheduling.
// 1. pulling out frame
// 2. if there's no frame, remove this stream from `buckets`
// 3. if forwarding this frame would make `streamCount` greater than `streamLimit`, remove
// 2. if there's no frame, skip this stream
// 3. if forwarding this frame would make `streamCount` greater than `streamLimit`, skip
// this stream
// 4. assigning an ID to the frame (allocating an ID to the stream if there isn't already)
// 5. if forwarding a PUSH_PROMISE, allocate ID to the promised stream
// 6. forwarding the frame, changing `streamCount` as appropriate
// 7. stepping to the next stream if there's still more frame needed in the output buffer
var index = 0;
// 4. adding stream to the bucket of the next round
// 5. assigning an ID to the frame (allocating an ID to the stream if there isn't already)
// 6. if forwarding a PUSH_PROMISE, allocate ID to the promised stream
// 7. forwarding the frame, changing `streamCount` as appropriate
// 8. stepping to the next stream if there's still more frame needed in the output buffer
// 9. switching to the bucket of the next round
while (bucket.length > 0) {
index = index % bucket.length;
var stream = bucket[index];
var frame = stream.upstream.read();
for (var index = 0; index < bucket.length; index++) {
var stream = bucket[index];
var frame = stream.upstream.read((this._window > 0) ? this._window : -1);
if (!frame) {
bucket.splice(index, 1);
}
if (!frame) {
continue;
} else if (frame.count_change > this._streamSlotsFree) {
stream.upstream.unshift(frame);
continue;
}
else if (this._streamCount + frame.count_change > this._streamLimit) {
stream.upstream.unshift(frame);
bucket.splice(index, 1);
}
nextBucket.push(stream);
else {
var id = this._streamIds.indexOf(stream);
if (id === -1) {
frame.stream = this._allocateId(stream);
} else {
frame.stream = id;
if (frame.stream === undefined) {
frame.stream = stream.id || this._allocateId(stream);
}

@@ -295,22 +326,22 @@

this._log.trace({ s: stream, frame: frame }, 'Trying to forward outgoing frame');
var remainder = null;
var moreNeeded = this._push(frame, function(remainderFrame) {
stream.upstream.unshift(remainder = remainderFrame);
});
if (!remainder) {
this._changeStreamCount(frame.count_change);
}
this._log.trace({ s: stream, frame: frame }, 'Forwarding outgoing frame');
var moreNeeded = this.push(frame);
this._changeStreamCount(frame.count_change);
if (moreNeeded === null) {
bucket.splice(index, 1);
} else if (moreNeeded === false) {
assert(moreNeeded !== null); // The frame shouldn't be unforwarded
if (moreNeeded === false) {
break priority_loop;
} else {
index += 1;
}
}
bucket = nextBucket;
nextBucket = [];
}
}
// * if we couldn't forward any frame, then sleep until window update, or some other wakeup event
if (moreNeeded === undefined) {
this.once('wakeup', this._send.bind(this));
}
this._log.trace({ moreNeeded: moreNeeded }, 'Stopping forwarding frames from streams.');

@@ -324,2 +355,8 @@ };

// * first frame needs to be checked by the `_onFirstFrameReceived` method
if (!this._firstFrameReceived) {
this._firstFrameReceived = true;
this._onFirstFrameReceived(frame);
}
// * gets the appropriate stream from the stream registry

@@ -359,12 +396,2 @@ var stream = this._streamIds[frame.stream];

// * Checking that the first frame the other endpoint sends is SETTINGS
this.once('receiving', function(frame) {
if ((frame.stream === 0) && (frame.type === 'SETTINGS')) {
this._log.debug('Receiving the first SETTINGS frame as part of the connection header.');
} else {
this._log.fatal({ frame: frame }, 'Invalid connection header: first frame is not SETTINGS.');
this.emit('error');
}
});
// * Forwarding SETTINGS frames to the `_receiveSettings` method

@@ -374,2 +401,12 @@ this.on('SETTINGS', this._receiveSettings);

// * Checking that the first frame the other endpoint sends is SETTINGS
Connection.prototype._onFirstFrameReceived = function _onFirstFrameReceived(frame) {
if ((frame.stream === 0) && (frame.type === 'SETTINGS')) {
this._log.debug('Receiving the first SETTINGS frame as part of the connection header.');
} else {
this._log.fatal({ frame: frame }, 'Invalid connection header: first frame is not SETTINGS.');
this.emit('error');
}
};
// Handling of incoming SETTINGS frames.

@@ -385,4 +422,5 @@ Connection.prototype._receiveSettings = function _receiveSettings(frame) {

this.push({
type: 'SETTINGS',
flags: {},
stream: 0,
type: 'SETTINGS',
settings: settings

@@ -409,2 +447,3 @@ });

this.on('GOAWAY', this._receiveGoaway);
this._closed = false;
};

@@ -431,3 +470,2 @@

this.push({
stream: 0,
type: 'PING',

@@ -437,2 +475,3 @@ flags: {

},
stream: 0,
data: data

@@ -448,3 +487,6 @@ });

this._log.debug({ data: frame.data }, 'Receiving answer for a PING.');
this._pings[id]();
var callback = this._pings[id];
if (callback) {
callback();
}
delete this._pings[id];

@@ -458,3 +500,2 @@ } else {

this.push({
stream: 0,
type: 'PING',

@@ -464,2 +505,3 @@ flags: {

},
stream: 0,
data: frame.data

@@ -472,6 +514,12 @@ });

Connection.prototype.close = function close(error) {
this._log.info({ error: error }, 'Closing the connection');
if (this._closed) {
this._log.warn('Trying to close an already closed connection');
return;
}
this._log.debug({ error: error }, 'Closing the connection');
this.push({
type: 'GOAWAY',
flags: {},
stream: 0,
type: 'GOAWAY',
last_stream: this._lastIncomingStream,

@@ -481,7 +529,12 @@ error: error || 'NO_ERROR'

this.push(null);
this._closed = true;
};
Connection.prototype._receiveGoaway = function _receiveGoaway(frame) {
this._log.info({ error: frame.error }, 'Other end closed the connection');
this._log.debug({ error: frame.error }, 'Other end closed the connection');
this.push(null);
this._closed = true;
if (frame.error !== 'NO_ERROR') {
this.emit('peerError', frame.error);
}
};

@@ -488,0 +541,0 @@

@@ -1,2 +0,2 @@

var assert = process.env.HTTP2_ASSERT ? require('assert') : function noop() {};
var assert = require('assert');

@@ -141,11 +141,15 @@ var Serializer = require('./framer').Serializer;

this._deserializer.pipe(this._decompressor).pipe(this._connection);
this._serializer.on('readable', this._read.bind(this));
};
var noread = {};
Endpoint.prototype._read = function _read() {
var moreNeeded = true, chunk;
this._readableState.sync = true;
var moreNeeded = noread, chunk;
while (moreNeeded && (chunk = this._serializer.read())) {
moreNeeded = this.push(chunk);
}
if (moreNeeded === noread) {
this._serializer.once('readable', this._read.bind(this));
}
this._readableState.sync = false;
};

@@ -177,7 +181,10 @@

this._connection.on('error', this._error.bind(this, 'connection'));
this._connection.on('peerError', this.emit.bind(this, 'peerError'));
};
Endpoint.prototype._error = function _error(component, error) {
this._log.fatal({ component: component, message: error }, 'Fatal error, closing connection');
this._log.fatal({ source: component, message: error }, 'Fatal error, closing connection');
this.close(error);
setImmediate(this.emit.bind(this, 'error', error));
};

@@ -184,0 +191,0 @@

@@ -1,2 +0,2 @@

var assert = process.env.HTTP2_ASSERT ? require('assert') : function noop() {};
var assert = require('assert');

@@ -34,6 +34,2 @@ // The Flow class

//
// * **Event: 'receiving' (frame)**: there's an incoming frame
//
// * **Event: 'sending' (frame)**: a frame was added to the output queue
//
// * **_send()**: called when more frames should be pushed. The child class is expected to override

@@ -51,12 +47,7 @@ // this (instead of the `_read` method of the Duplex class).

//
// * **_push(frame, remainderCallback): bool**: like push, but never puts the frame into the
// flow control queue.
// * **read(limit): frame**: like the regular `read`, but the 'flow control size' (0 for non-DATA
// frames, length of the payload for DATA frames) of the returned frame will be under `limit`.
// Small exception: pass -1 as `limit` if the max. flow control size is 0. `read(0)` means the
// same thing as [in the original API](http://nodejs.org/api/stream.html#stream_stream_read_0).
//
// Instead, it pushes directly into the output queue if possible (according to the flow control
// window) and calls `remainderCallback` with the remaining non-pushable part of the frame. It is
// capable of dividing DATA frames into multiple chunks.
//
// Use this instead of `push` if you always want to have empty flow control queue (but never mix
// the two).
//
// * **getLastQueuedFrame(): frame**: returns the last frame in output buffers

@@ -80,3 +71,2 @@ //

this._queue = [];
this._ended = false;

@@ -101,4 +91,2 @@ this._received = 0;

Flow.prototype._write = function _write(frame, encoding, callback) {
this.emit('receiving', frame);
if (frame.flags.END_STREAM) {

@@ -135,2 +123,3 @@ this._ended = true;

type: 'WINDOW_UPDATE',
flags: {},
stream: this._flowControlId,

@@ -184,74 +173,114 @@ window_size: this._received

else if (this._window > 0) {
var frame;
var moreNeeded = true;
var unshiftRemainder = this._queue.unshift.bind(this._queue);
while (moreNeeded && (frame = this._queue.shift())) {
moreNeeded = this._push(frame, unshiftRemainder);
}
this._readableState.sync = true; // to avoid reentrant calls
do {
var moreNeeded = this._push(this._queue[0]);
if (moreNeeded !== null) {
this._queue.shift();
}
} while (moreNeeded && (this._queue.length > 0));
this._readableState.sync = false;
assert((moreNeeded == false) || // output queue is full
(this._queue.length === 0) || // flow control queue is empty
((this._window === 0) && (this._queue[0].type === 'DATA'))); // waiting for window update
assert((moreNeeded == false) || // * output queue is full
(this._queue.length === 0) || // * flow control queue is empty
(!this._window && (this._queue[0].type === 'DATA'))); // * waiting for window update
}
this._readableState.reading = false;
// * otherwise, come back when the flow control window is positive
else {
this.once('window_update', this._read);
}
};
// `_push(frame)` is the low-level version of `push`. Use this instead of `push` if you always want
// to have empty flow control queue (but never mix the two). It pushes `frame` into the output queue
// and decreases the flow control window size. It is capable of splitting DATA frames into smaller
// parts, if the window size is not enough to push the whole frame. It calls `remainderCallback`
// synchronously before returning with the frame it was not able to push to the output queue. The
// remainder may be the whole frame or the remaining part of a DATA frame. The return value is
// similar to `push` except that it returns `null` if it did not push anything to the output queue.
var MAX_PAYLOAD_SIZE = 4096; // Must not be greater than MAX_HTTP_PAYLOAD_SIZE which is 16383
Flow.prototype._push = function _push(frame, remainderCallback) {
do {
var forwardable = undefined, remainder = undefined;
if ((frame === null) || (frame.type !== 'DATA') ||
((frame.data.length <= this._window) && (frame.data.length <= MAX_PAYLOAD_SIZE))) {
forwardable = frame;
// `read(limit)` is like the `read` of the Readable class, but it guarantess that the 'flow control
// size' (0 for non-DATA frames, length of the payload for DATA frames) of the returned frame will
// be under `limit`.
Flow.prototype.read = function read(limit) {
if (limit === 0) {
return Duplex.prototype.read.call(this, 0);
} else if (limit === -1) {
limit = 0;
} else if ((limit === undefined) || (limit > MAX_PAYLOAD_SIZE)) {
limit = MAX_PAYLOAD_SIZE;
}
// * Looking at the first frame in the queue without pulling it out if possible. This will save
// a costly unshift if the frame proves to be too large to return.
var firstInQueue = this._readableState.buffer[0];
var frame = firstInQueue || Duplex.prototype.read.call(this);
if ((frame === null) || (frame.type !== 'DATA') || (frame.data.length <= limit)) {
if (firstInQueue) {
Duplex.prototype.read.call(this);
}
return frame;
}
else if (this._window <= 0) {
remainder = frame;
else if (limit <= 0) {
if (!firstInQueue) {
this.unshift(frame);
}
return null;
}
else {
var chunkSize = Math.min(this._window, MAX_PAYLOAD_SIZE);
forwardable = {
stream: frame.stream,
type: 'DATA',
flags: {},
data: frame.data.slice(0, chunkSize)
};
else {
this._log.trace({ frame: frame, size: frame.data.length, forwardable: limit },
'Splitting out forwardable part of a DATA frame.');
var forwardable = {
type: 'DATA',
flags: {},
stream: frame.stream,
data: frame.data.slice(0, limit)
};
frame.data = frame.data.slice(limit);
this._log.trace({ frame: frame, size: frame.data.length, forwardable: chunkSize },
'Splitting out forwardable part of a DATA frame.');
frame.data = frame.data.slice(chunkSize);
remainder = frame;
if (!firstInQueue) {
this.unshift(frame);
}
return forwardable;
}
};
var moreNeeded = null;
if (forwardable !== undefined) {
this._log.trace({ frame: forwardable }, 'Pushing frame into the output queue');
if (forwardable && (forwardable.type === 'DATA') && (this._window !== Infinity)) {
this._log.trace({ window: this._window, by: forwardable.data.length },
'Decreasing flow control window size.');
this._window -= forwardable.data.length;
assert(this._window >= 0);
}
moreNeeded = Duplex.prototype.push.call(this, forwardable);
}
// `_parentPush` pushes the given `frame` into the output queue
Flow.prototype._parentPush = function _parentPush(frame) {
this._log.trace({ frame: frame }, 'Pushing frame into the output queue');
frame = remainder;
} while (remainder && moreNeeded);
if (frame && (frame.type === 'DATA') && (this._window !== Infinity)) {
this._log.trace({ window: this._window, by: frame.data.length },
'Decreasing flow control window size.');
this._window -= frame.data.length;
assert(this._window >= 0);
}
if (remainder !== undefined) {
remainderCallback(remainder);
return Duplex.prototype.push.call(this, frame);
};
// `_push(frame)` pushes `frame` into the output queue and decreases the flow control window size.
// It is capable of splitting DATA frames into smaller parts, if the window size is not enough to
// push the whole frame. The return value is similar to `push` except that it returns `null` if it
// did not push the whole frame to the output queue (but maybe it did push part of the frame).
Flow.prototype._push = function _push(frame) {
var data = frame && (frame.type === 'DATA') && frame.data;
if (!data || (data.length <= this._window)) {
return this._parentPush(frame);
}
return moreNeeded;
else if (this._window <= 0) {
return null;
}
else {
this._log.trace({ frame: frame, size: frame.data.length, forwardable: this._window },
'Splitting out forwardable part of a DATA frame.');
frame.data = data.slice(this._window);
this._parentPush({
type: 'DATA',
flags: {},
stream: frame.stream,
data: data.slice(0, this._window)
});
return null;
}
};

@@ -264,13 +293,15 @@

} else {
frame.flags = frame.flags || {};
this._log.debug({ frame: frame }, 'Enqueueing outgoing frame');
this.emit('sending', frame);
}
var moreNeeded = null;
if (this._queue.length === 0) {
return this._push(frame, this._queue.push.bind(this._queue));
} else {
moreNeeded = this._push(frame);
}
if (moreNeeded === null) {
this._queue.push(frame);
return null;
}
return moreNeeded;
};

@@ -309,3 +340,3 @@

} else {
this.read(0);
this.emit('window_update');
}

@@ -312,0 +343,0 @@ }

@@ -5,3 +5,3 @@ // The framer consists of two [Transform Stream][1] subclasses that operate in [object mode][2]:

// [2]: http://nodejs.org/api/stream.html#stream_new_stream_readable_options
var assert = process.env.HTTP2_ASSERT ? require('assert') : function noop() {};
var assert = require('assert');

@@ -113,7 +113,8 @@ var Transform = require('stream').Transform;

if ((this._cursor === this._buffer.length) && this._waitingForHeader) {
Deserializer.commonHeader(this._buffer, this._frame);
if (this._frame.length <= this._sizeLimit) {
this._next(this._frame.length);
var payloadSize = Deserializer.commonHeader(this._buffer, this._frame);
if (payloadSize <= this._sizeLimit) {
this._next(payloadSize);
} else {
this.emit('error', 'FRAME_TOO_LARGE');
return;
}

@@ -131,3 +132,3 @@ }

if (error) {
this._log.error('Incoming frame parsing error');
this._log.error('Incoming frame parsing error: ' + error);
this.emit('error', 'PROTOCOL_ERROR');

@@ -200,3 +201,3 @@ } else {

var genericAttributes = ['length', 'type', 'flags', 'stream'];
var genericAttributes = ['type', 'flags', 'stream'];

@@ -212,3 +213,3 @@ var typeSpecificAttributes = {};

}
assert(size <= MAX_PAYLOAD_SIZE, 'Frame too large!');
assert(size <= MAX_PAYLOAD_SIZE, size);
headerBuffer.writeUInt16BE(size, 0);

@@ -229,3 +230,3 @@

assert(frame.stream < 0x7fffffff, 'Too large stream ID: ' + frame.stream);
assert((0 <= frame.stream) && (frame.stream < 0x7fffffff), frame.stream);
headerBuffer.writeUInt32BE(frame.stream || 0, 4);

@@ -237,3 +238,3 @@

Deserializer.commonHeader = function readCommonHeader(buffer, frame) {
frame.length = buffer.readUInt16BE(0);
var length = buffer.readUInt16BE(0);

@@ -250,2 +251,4 @@ frame.type = frameTypes[buffer.readUInt8(2)];

frame.stream = buffer.readUInt32BE(4) & 0x7fffffff;
return length;
};

@@ -329,3 +332,3 @@

var buffer = new Buffer(4);
assert((0 <= frame.priority) && (frame.priority <= 0xffffffff));
assert((0 <= frame.priority) && (frame.priority <= 0xffffffff), frame.priority);
buffer.writeUInt32BE(frame.priority, 0);

@@ -402,3 +405,3 @@ buffers.push(buffer);

var code = errorCodes.indexOf(frame.error);
assert((0 <= code) && (code <= 0xffffffff));
assert((0 <= code) && (code <= 0xffffffff), code);
buffer.writeUInt32BE(code, 0);

@@ -531,4 +534,7 @@ buffers.push(buffer);

var buffer = new Buffer(4);
assert((0 <= frame.promised_stream) && (frame.promised_stream <= 0x7fffffff));
buffer.writeUInt32BE(frame.promised_stream, 0);
var promised_stream = frame.promised_stream;
assert((0 <= promised_stream) && (promised_stream <= 0x7fffffff), promised_stream);
buffer.writeUInt32BE(promised_stream, 0);
buffers.push(buffer);

@@ -563,3 +569,2 @@ buffers.push(frame.data);

Serializer.PING = function writePing(frame, buffers) {
assert(('data' in frame) && (frame.data.length === 8), 'PING frames must carry an 8 byte payload');
buffers.push(frame.data);

@@ -606,7 +611,8 @@ };

assert((0 <= frame.last_stream) && (frame.last_stream <= 0x7fffffff));
buffer.writeUInt32BE(frame.last_stream, 0);
var last_stream = frame.last_stream;
assert((0 <= last_stream) && (last_stream <= 0x7fffffff), last_stream);
buffer.writeUInt32BE(last_stream, 0);
var code = errorCodes.indexOf(frame.error);
assert((0 <= code) && (code <= 0xffffffff));
assert((0 <= code) && (code <= 0xffffffff), code);
buffer.writeUInt32BE(code, 4);

@@ -642,4 +648,7 @@

var buffer = new Buffer(4);
assert((0 <= frame.window_size) && (frame.window_size <= 0x7fffffff));
buffer.writeUInt32BE(frame.window_size, 0);
var window_size = frame.window_size;
assert((0 <= window_size) && (window_size <= 0x7fffffff), window_size);
buffer.writeUInt32BE(window_size, 0);
buffers.push(buffer);

@@ -646,0 +655,0 @@ };

@@ -92,3 +92,3 @@ // Public API

// - **Event: 'connect'**: not in the spec, yet (see [http-spec#230][connect])
// - **server.setTimeout(msecs, callback)**
// - **server.setTimeout(msecs, [callback])**
// - **server.timeout**

@@ -143,2 +143,3 @@ //

exports.OutgoingMessage = OutgoingMessage;
exports.Endpoint = Endpoint;

@@ -243,2 +244,12 @@ var deprecatedHeaders = [

IncomingMessage.prototype._checkSpecialHeader = function _checkSpecialHeader(key, value) {
if ((typeof value !== 'string') || (value.length === 0)) {
this._log.error({ key: key, value: value }, 'Invalid special header field');
this.stream.emit('error', 'PROTOCOL_ERROR');
}
return value;
}
;
// OutgoingMessage class

@@ -255,3 +266,3 @@ // ---------------------

this.on('finish', this._finish.bind(this));
this.on('finish', this._finish);
}

@@ -313,2 +324,4 @@ OutgoingMessage.prototype = Object.create(Writable.prototype, { constructor: { value: OutgoingMessage } });

OutgoingMessage.prototype._checkSpecialHeader = IncomingMessage.prototype._checkSpecialHeader;
// Server side

@@ -448,3 +461,3 @@ // ===========

if ((event === 'upgrade') || (event === 'timeout')) {
this._server.on(event, listener.bind(this));
this._server.on(event, listener && listener.bind(this));
} else {

@@ -500,17 +513,6 @@ EventEmitter.prototype.on.call(this, event, listener);

// an invalid value as a stream error of type PROTOCOL_ERROR.
var mapping = {
':method': 'method',
':scheme': 'scheme',
':host': 'host',
':path': 'url'
};
for (var name in mapping) {
var value = headers[name];
if ((typeof value !== 'string') || (value.length === 0)) {
this._log.error({ key: name, value: value }, 'Invalid or missing special header field');
this.stream.emit('error', 'PROTOCOL_ERROR');
return;
}
this[mapping[name]] = value;
}
this.method = this._checkSpecialHeader(':method', headers[':method']);
this.scheme = this._checkSpecialHeader(':scheme', headers[':scheme']);
this.host = this._checkSpecialHeader(':host' , headers[':host'] );
this.url = this._checkSpecialHeader(':path' , headers[':path'] );

@@ -617,3 +619,3 @@ // * Host header is included in the headers object for backwards compatibility.

if (this.request && (event === 'timeout')) {
this.request.on(event, listener.bind(this));
this.request.on(event, listener && listener.bind(this));
} else {

@@ -858,3 +860,3 @@ OutgoingMessage.prototype.on.call(this, event, listener);

if (this.request && (event === 'upgrade')) {
this.request.on(event, listener.bind(this));
this.request.on(event, listener && listener.bind(this));
} else {

@@ -933,9 +935,3 @@ OutgoingMessage.prototype.on.call(this, event, listener);

// status line.
var statusCode = headers[':status'];
if ((typeof statusCode !== 'string') || (statusCode.length === 0)) {
this._log.error({ key: ':status', value: statusCode }, 'Invalid header field');
this.stream.emit('error', 'PROTOCOL_ERROR');
return;
}
this.statusCode = statusCode;
this.statusCode = this._checkSpecialHeader(':status', headers[':status']);

@@ -942,0 +938,0 @@ // * Handling regular headers.

@@ -1,2 +0,2 @@

var assert = process.env.HTTP2_ASSERT ? require('assert') : function noop() {};
var assert = require('assert');

@@ -77,14 +77,2 @@ // The Stream class

this._letPeerPrioritize = true;
this.on('PUSH_PROMISE', function(frame) {
this.emit('promise', frame.promised_stream, frame.headers);
});
this.on('HEADERS', function(frame) {
if (frame.priority !== undefined) {
this.priority(frame.priority, true);
}
this.emit('headers', frame.headers);
});
this.on('PRIORITY', function(frame) {
this.priority(frame.priority, true);
});
};

@@ -95,4 +83,6 @@

stream._priority = Math.min(this._priority + 1, MAX_PRIORITY);
this.upstream.push({
this._pushUpstream({
type: 'PUSH_PROMISE',
flags: {},
stream: this.id,
promised_stream: stream,

@@ -104,5 +94,11 @@ headers: headers

Stream.prototype._onPromise = function _onPromise(frame) {
this.emit('promise', frame.promised_stream, frame.headers);
};
Stream.prototype.headers = function headers(headers) {
this.upstream.push({
this._pushUpstream({
type: 'HEADERS',
flags: {},
stream: this.id,
headers: headers

@@ -112,2 +108,9 @@ });

Stream.prototype._onHeaders = function _onHeaders(frame) {
if (frame.priority !== undefined) {
this.priority(frame.priority, true);
}
this.emit('headers', frame.headers);
};
Stream.prototype.priority = function priority(priority, peer) {

@@ -122,4 +125,6 @@ if ((peer && this._letPeerPrioritize) || !peer) {

} else {
this.upstream.push({
this._pushUpstream({
type: 'PRIORITY',
flags: {},
stream: this.id,
priority: priority

@@ -136,2 +141,6 @@ });

Stream.prototype._onPriority = function _onPriority(frame) {
this.priority(frame.priority, true);
};
// Resetting the stream. Normally, an endpoint SHOULD NOT send more than one RST_STREAM frame for

@@ -142,4 +151,6 @@ // any stream.

this._resetSent = true;
this.upstream.push({
this._pushUpstream({
type: 'RST_STREAM',
flags: {},
stream: this.id,
error: error

@@ -186,2 +197,4 @@ });

Stream.prototype._initializeDataFlow = function _initializeDataFlow() {
this.id = undefined;
this.upstream = new Flow();

@@ -191,4 +204,2 @@ this.upstream._log = this._log;

this.upstream._receive = this._receive.bind(this);
this.upstream.on('sending', this.emit.bind(this, 'sending'));
this.upstream.on('receiving', this.emit.bind(this, 'receiving'));
this.upstream.on('error', this.emit.bind(this, 'error'));

@@ -199,2 +210,7 @@

Stream.prototype._pushUpstream = function _pushUpstream(frame) {
this.upstream.push(frame);
this._transition(true, frame);
};
// The `_receive` method (= `upstream._receive`) gets called when there's an incoming frame.

@@ -204,2 +220,4 @@ Stream.prototype._receive = function _receive(frame, ready) {

this._transition(false, frame);
var callReady = true;

@@ -217,7 +235,17 @@

// * Otherwise it's a control frame. Emit an event to notify interested parties.
else {
this.emit(frame.type, frame);
// * Otherwise it's a control frame. Call the appropriate handler method.
else if (frame.type === 'HEADERS') {
this._onHeaders(frame);
} else if (frame.type === 'PUSH_PROMISE') {
this._onPromise(frame);
} else if (frame.type === 'PRIORITY') {
this._onPriority(frame);
}
// * If it's an invalid stream level frame, emit error
else if (frame.type !== 'WINDOW_UPDATE') {
this._log.error({ frame: frame }, 'Invalid stream level frame');
this.emit('error', 'PROTOCOL_ERROR');
}
// * Any frame may signal the end of the stream with the END_STREAM flag

@@ -246,4 +274,6 @@ if (frame.flags.END_STREAM) {

// * Chunking is done by the upstream Flow.
var moreNeeded = this.upstream.push({
var moreNeeded = this._pushUpstream({
type: 'DATA',
flags: {},
stream: this.id,
data: buffer

@@ -275,2 +305,3 @@ });

// existing frame is a nice optimization.
var emptyBuffer = new Buffer(0);
Stream.prototype._finishing = function _finishing() {

@@ -280,3 +311,4 @@ var endFrame = {

flags: { END_STREAM: true },
data: new Buffer(0)
stream: this.id,
data: emptyBuffer
};

@@ -286,7 +318,6 @@ var lastFrame = this.upstream.getLastQueuedFrame();

this._log.debug({ frame: lastFrame }, 'Marking last frame with END_STREAM flag.');
lastFrame.flags = lastFrame.flags || {};
lastFrame.flags.END_STREAM = true;
this._transition(true, endFrame);
} else {
this.upstream.push(endFrame);
this._pushUpstream(endFrame);
}

@@ -330,4 +361,2 @@ };

this._closedWithRst = undefined;
this.on('sending', this._transition.bind(this, true));
this.on('receiving', this._transition.bind(this, false));
};

@@ -582,7 +611,7 @@

exports.serializers.s = function(stream) {
if (!('id' in stream)) {
stream.id = nextId;
if (!('_id' in stream)) {
stream._id = nextId;
nextId += 1;
}
return stream.id;
return stream._id;
};
{
"name": "http2",
"version": "0.4.0",
"version": "0.4.1",
"description": "An HTTP/2 client and server implementation",

@@ -17,3 +17,3 @@ "main": "lib/index.js",

"scripts": {
"test": "HTTP2_ASSERT=1 istanbul test _mocha -- --reporter spec --slow 200",
"test": "istanbul test _mocha -- --reporter spec --slow 200",
"prepublish": "docco lib/* --output doc --layout parallel --css doc/docco.css"

@@ -20,0 +20,0 @@ },

@@ -30,4 +30,2 @@ node-http2

```javascript
var http2 = require('http2');
var options = {

@@ -38,3 +36,3 @@ key: fs.readFileSync('./example/localhost.key'),

http2.createServer(options, function(request, response) {
require('http2').createServer(options, function(request, response) {
response.end('Hello world!');

@@ -47,9 +45,5 @@ }).listen(8080);

```javascript
var http2 = require('http2');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var request = http2.get('https://localhost:8080/');
request.on('response', function(response) {
require('http2').get('https://localhost:8080/', function(response) {
response.pipe(process.stdout);

@@ -128,8 +122,8 @@ });

To generate a code coverage report, run `npm test --coverage` (which runs very slowly, be patient).
Code coverage summary as of version 0.4.0:
Code coverage summary as of version 0.4.1:
```
Statements : 93.44% ( 1482/1586 )
Branches : 86.13% ( 559/649 )
Functions : 92.34% ( 193/209 )
Lines : 93.42% ( 1476/1580 )
Statements : 93.33% ( 1538/1648 )
Branches : 84.91% ( 585/689 )
Functions : 95.65% ( 198/207 )
Lines : 93.3% ( 1532/1642 )
```

@@ -148,4 +142,4 @@

To log every single incoming and outgoing data chunk, use `HTTP2_LOG_DATA=1` besides
`HTTP2_LOG=trace`. Log output goes to the standard error output, and is in JSON format. It can be
pretty printed using the bunyan command line tool.
`HTTP2_LOG=trace`. Log output goes to the standard error output. If the standard error is redirected
into a file, then the log output is in bunyan's JSON format for easier post-mortem analysis.

@@ -155,8 +149,7 @@ Running the example server and client with `info` level logging output:

```bash
$ HTTP2_LOG=info node ./example/server.js 2> >(bunyan -o short)
$ HTTP2_LOG=info node ./example/server.js
```
```bash
$ HTTP2_LOG=info node ./example/client.js 'http://localhost:8080/server.js' \
>/dev/null 2> >(bunyan -o short)
$ HTTP2_LOG=info node ./example/client.js 'http://localhost:8080/server.js' >/dev/null
```

@@ -163,0 +156,0 @@

@@ -106,3 +106,3 @@ var expect = require('chai').expect;

var buffer = new Buffer(10);
var dataFrame = { stream: util.random(0, 100), type: 'DATA', flags: {}, data: buffer };
var dataFrame = { type: 'DATA', flags: {}, stream: util.random(0, 100), data: buffer };
flow._send = util.noop;

@@ -112,3 +112,3 @@ flow._window = 5;

var expectedFragment = { stream: dataFrame.stream, flags: {}, type: 'DATA', data: buffer.slice(0,5) };
var expectedFragment = { flags: {}, type: 'DATA', stream: dataFrame.stream, data: buffer.slice(0,5) };
expect(flow.read()).to.deep.equal(expectedFragment);

@@ -148,6 +148,6 @@ expect(dataFrame.data).to.deep.equal(buffer.slice(5));

expect(flow.read()).to.be.deep.equal({
type: 'WINDOW_UPDATE',
flags: {},
stream: flow._flowControlId,
type: 'WINDOW_UPDATE',
window_size: buffer.length,
flags: {}
window_size: buffer.length
});

@@ -168,3 +168,2 @@ done();

flow1._receive = flow2._receive = function(frame, callback) { callback(); };
flow1.pipe(flow2).pipe(flow1);
});

@@ -211,3 +210,3 @@

// Start piping
flow1.read(0);
flow1.pipe(flow2).pipe(flow1);
});

@@ -214,0 +213,0 @@ });

@@ -26,3 +26,2 @@ var expect = require('chai').expect;

stream: 10,
length: 4,

@@ -39,3 +38,2 @@ data: new Buffer('12345678', 'hex')

stream: 15,
length: 4,

@@ -51,3 +49,2 @@ data: new Buffer('12345678', 'hex')

stream: 15,
length: 8,

@@ -64,3 +61,2 @@ priority: 3,

stream: 10,
length: 4,

@@ -76,3 +72,2 @@ priority: 3

stream: 10,
length: 4,

@@ -88,3 +83,2 @@ error: 'INTERNAL_ERROR'

stream: 10,
length: 24,

@@ -106,3 +100,2 @@ settings: {

stream: 15,
length: 8,

@@ -119,3 +112,2 @@ promised_stream: 3,

stream: 15,
length: 8,

@@ -131,3 +123,2 @@ data: new Buffer('1234567887654321', 'hex')

stream: 10,
length: 8,

@@ -144,3 +135,2 @@ last_stream: 0x12345678,

stream: 10,
length: 4,

@@ -155,3 +145,2 @@ window_size: 0x12345678

stream: 10,
length: 4,

@@ -164,15 +153,2 @@ data: new Buffer('12345678', 'hex')

// Concatenate an array of buffers and then cut them into random size buffers
function shuffle_buffers(buffers) {
var concatenated = util.concat(buffers), output = [], written = 0;
while (written < concatenated.length) {
var chunk_size = Math.min(concatenated.length - written, Math.ceil(Math.random()*20));
output.push(concatenated.slice(written, written + chunk_size));
written += chunk_size;
}
return output;
}
describe('framer.js', function() {

@@ -226,3 +202,3 @@ describe('Serializer', function() {

describe('static method .commonHeader(header_buffer, frame)', function() {
it('should augment the frame object with these properties: { length, type, flags, stream })', function() {
it('should augment the frame object with these properties: { type, flags, stream })', function() {
for (var i = 0; i < test_frames.length; i++) {

@@ -232,3 +208,2 @@ var test = test_frames[i], frame = {};

expect(frame).to.deep.equal({
length: test.frame.length,
type: test.frame.type,

@@ -250,3 +225,2 @@ flags: test.frame.flags,

var frame = {
length: test.frame.length,
type: test.frame.type,

@@ -267,9 +241,7 @@ flags: test.frame.flags,

var shuffled = shuffle_buffers(test_frames.map(function(test) { return test.buffer; }));
var shuffled = util.shuffleBuffers(test_frames.map(function(test) { return test.buffer; }));
shuffled.forEach(stream.write.bind(stream));
for (var j = 0; j < test_frames.length; j++) {
var parsed_frame = stream.read();
parsed_frame.length = test_frames[j].frame.length;
expect(parsed_frame).to.be.deep.equal(test_frames[j].frame);
expect(stream.read()).to.be.deep.equal(test_frames[j].frame);
}

@@ -276,0 +248,0 @@ });

@@ -0,1 +1,5 @@

var path = require('path');
var fs = require('fs');
var spawn = require('child_process').spawn;
function noop() {}

@@ -5,9 +9,24 @@ exports.noop = noop;

if (process.env.HTTP2_LOG) {
exports.log = require('bunyan').createLogger({
name: 'test',
stream: process.stderr,
level: process.env.HTTP2_LOG,
serializers: require('../lib/http').serializers
});
var logOutput = process.stderr;
if (process.stderr.isTTY) {
var bin = path.resolve(path.dirname(require.resolve('bunyan')), '..', 'bin', 'bunyan');
if(bin && fs.existsSync(bin)) {
logOutput = spawn(bin, ['-o', 'short'], {
stdio: [null, process.stderr, process.stderr]
}).stdin;
}
}
exports.createLogger = function(name) {
return require('bunyan').createLogger({
name: name,
stream: logOutput,
level: process.env.HTTP2_LOG,
serializers: require('../lib/http').serializers
});
};
exports.log = exports.createLogger('test');
} else {
exports.createLogger = function() {
return exports.log;
};
exports.log = {

@@ -57,1 +76,14 @@ fatal: noop,

};
// Concatenate an array of buffers and then cut them into random size buffers
exports.shuffleBuffers = function shuffleBuffers(buffers) {
var concatenated = exports.concat(buffers), output = [], written = 0;
while (written < concatenated.length) {
var chunk_size = Math.min(concatenated.length - written, Math.ceil(Math.random()*20));
output.push(concatenated.slice(written, written + chunk_size));
written += chunk_size;
}
return output;
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc