Bug 1046892 part 2 - HTTP/2 draft 14 test updates r=mcmanus

This commit is contained in:
Nicholas Hurley 2014-08-05 08:41:11 -07:00
parent 8446acda81
commit 41eb41171a
20 changed files with 428 additions and 469 deletions

View File

@ -25,7 +25,7 @@ var bigListenerMD5 = '8f607cfdd2c87d6a7eedb657dafbd836';
function checkIsHttp2(request) {
try {
if (request.getResponseHeader("X-Firefox-Spdy") == "h2-13") {
if (request.getResponseHeader("X-Firefox-Spdy") == "h2-14") {
if (request.getResponseHeader("X-Connection-Http2") == "yes") {
return true;
}

View File

@ -0,0 +1,5 @@
language: node_js
node_js:
- "0.11"
- "0.10"

View File

@ -1,6 +1,16 @@
Version history
===============
### 2.7.1 (2014-08-01) ###
* Require protocol 0.14.1 (bugfix release)
### 2.7.0 (2014-07-31) ###
* Upgrade to the latest draft: [draft-ietf-httpbis-http2-14]
[draft-ietf-httpbis-http2-14]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14
### 2.6.0 (2014-06-18) ###
* Upgrade to the latest draft: [draft-ietf-httpbis-http2-13]

View File

@ -1,9 +1,11 @@
node-http2
==========
An HTTP/2 ([draft-ietf-httpbis-http2-13](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13))
An HTTP/2 ([draft-ietf-httpbis-http2-14](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14))
client and server implementation for node.js.
![Travis CI status](https://travis-ci.org/molnarg/node-http2.svg?branch=master)
Installation
------------
@ -114,12 +116,12 @@ point to understand the code.
### Test coverage ###
To generate a code coverage report, run `npm test --coverage` (which runs very slowly, be patient).
Code coverage summary as of version 2.5.3:
Code coverage summary as of version 2.7.0:
```
Statements : 92.64% ( 403/435 )
Branches : 79.41% ( 135/170 )
Statements : 92.68% ( 405/437 )
Branches : 79.65% ( 137/172 )
Functions : 92.31% ( 60/65 )
Lines : 92.64% ( 403/435 )
Lines : 92.68% ( 405/437 )
```
There's a hosted version of the detailed (line-by-line) coverage report

View File

@ -121,7 +121,7 @@
//
// [1]: http://nodejs.org/api/https.html
// [2]: http://nodejs.org/api/http.html
// [3]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-8.1.3.2
// [3]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.2
// [expect-continue]: https://github.com/http2/http2-spec/issues/18
// [connect]: https://github.com/http2/http2-spec/issues/230
@ -246,7 +246,7 @@ function IncomingMessage(stream) {
}
IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } });
// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-8.1.3.1)
// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.1)
// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series
// of key-value pairs. This includes the target URI for the request, the status code for the
// response, as well as HTTP header fields.
@ -560,7 +560,7 @@ function IncomingRequest(stream) {
}
IncomingRequest.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingRequest } });
// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-8.1.3.1)
// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.1)
// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series
// of key-value pairs. This includes the target URI for the request, the status code for the
// response, as well as HTTP header fields.
@ -609,6 +609,10 @@ function OutgoingResponse(stream) {
OutgoingResponse.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingResponse } });
OutgoingResponse.prototype.writeHead = function writeHead(statusCode, reasonPhrase, headers) {
if (this.headersSent) {
return;
}
if (typeof reasonPhrase === 'string') {
this._log.warn('Reason phrase argument was present but ignored by the writeHead method');
} else {
@ -999,7 +1003,7 @@ function IncomingResponse(stream) {
}
IncomingResponse.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingResponse } });
// [Response Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-8.1.3.2)
// [Response Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.2)
// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series
// of key-value pairs. This includes the target URI for the request, the status code for the
// response, as well as HTTP header fields.

View File

@ -1,4 +1,4 @@
// [node-http2][homepage] is an [HTTP/2 (draft 13)][http2] implementation for [node.js][node].
// [node-http2][homepage] is an [HTTP/2 (draft 14)][http2] implementation for [node.js][node].
//
// The core of the protocol is implemented by the [http2-protocol] module. This module provides
// two important features on top of http2-protocol:
@ -11,7 +11,7 @@
//
// [homepage]: https://github.com/molnarg/node-http2
// [http2-protocol]: https://github.com/molnarg/node-http2-protocol
// [http2]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-13
// [http2]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14
// [node]: http://nodejs.org/
// [node-https]: http://nodejs.org/api/https.html
// [node-http]: http://nodejs.org/api/http.html

View File

@ -1,6 +1,16 @@
Version history
===============
### 0.14.1 (2014-08-01) ###
* Fixed an error that caused us to send :-headers after non-:-headers
### 0.14.0 (2014-07-31) ###
* Upgrade to the latest draft: [draft-ietf-httpbis-http2-14][draft-14]
[draft-14]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14
### 0.13.0 (2014-06-18) ###
* Upgrade to the latest draft: [draft-ietf-httpbis-http2-13][draft-13]

View File

@ -1,9 +1,11 @@
node-http2-protocol
===================
An HTTP/2 ([draft-ietf-httpbis-http2-13](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13))
An HTTP/2 ([draft-ietf-httpbis-http2-14](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14))
framing layer implementaion for node.js.
![Travis CI status](https://travis-ci.org/molnarg/node-http2-protocol.svg?branch=master)
Installation
------------

View File

@ -49,37 +49,8 @@ function HeaderTable(log, limit) {
return self;
}
// There are few more sets that are needed for the compression/decompression process that are all
// subsets of the Header Table, and are implemented as flags on header table entries:
//
// * [Reference Set][referenceset]: contains a group of headers used as a reference for the
// differential encoding of a new set of headers. (`reference` flag)
// * Emitted headers: the headers that are already emitted as part of the current decompression
// process (not part of the spec, `emitted` flag)
// * Headers to be kept: headers that should not be removed as the last step of the encoding process
// (not part of the spec, `keep` flag)
//
// [referenceset]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.1.3
//
// Relations of the sets:
//
// ,----------------------------------.
// | Header Table |
// | |
// | ,----------------------------. |
// | | Reference Set | |
// | | | |
// | | ,---------. ,---------. | |
// | | | Keep | | Emitted | | |
// | | | | | | | |
// | | `---------' `---------' | |
// | `----------------------------' |
// `----------------------------------'
function entryFromPair(pair) {
var entry = pair.slice();
entry.reference = false;
entry.emitted = false;
entry.keep = false;
entry._size = size(entry);
return entry;
}
@ -108,22 +79,20 @@ function size(entry) {
// `(this._limit - entry.size)`, or until the table is empty.
//
// <---------- Index Address Space ---------->
// <-- Header Table --> <-- Static Table -->
// <-- Static Table --> <-- Header Table -->
// +---+-----------+---+ +---+-----------+---+
// | 0 | ... | k | |k+1| ... | n |
// +---+-----------+---+ +---+-----------+---+
// ^ |
// | V
// Insertion Point Drop Point
// ^ |
// | V
// Insertion Point Drop Point
HeaderTable.prototype._enforceLimit = function _enforceLimit(limit) {
var droppedEntries = [];
var dropPoint = this.length - this._staticLength;
while ((this._size > limit) && (dropPoint > 0)) {
dropPoint -= 1;
var dropped = this.splice(dropPoint, 1)[0];
while ((this._size > 0) && (this._size > limit)) {
var dropped = this.pop();
this._size -= dropped._size;
droppedEntries[dropPoint] = dropped;
droppedEntries.unshift(dropped);
}
return droppedEntries;
};
@ -133,7 +102,7 @@ HeaderTable.prototype.add = function(entry) {
var droppedEntries = this._enforceLimit(limit);
if (this._size <= limit) {
this.unshift(entry);
this.splice(this._staticLength, 0, entry);
this._size += entry._size;
}
@ -262,7 +231,6 @@ HeaderSetDecompressor.prototype._transform = function _transform(chunk, encoding
//
// Indexed:
// { name: 2 , value: 2 , index: false }
// { name: -1 , value: -1 , index: false } // reference set emptying
// Literal:
// { name: 2 , value: 'X', index: false } // without indexing
// { name: 2 , value: 'Y', index: true } // with indexing
@ -274,48 +242,17 @@ HeaderSetDecompressor.prototype._execute = function _execute(rep) {
var entry, pair;
if (rep.contextUpdate) {
if (rep.clearReferenceSet) {
for (var i = 0; i < this._table.length; i++) {
this._table[i].reference = false;
}
} else {
this.setTableSizeLimit(rep.newMaxSize);
}
this.setTableSizeLimit(rep.newMaxSize);
}
// * An _indexed representation_ corresponding to an entry _present_ in the reference set
// entails the following actions:
// * The entry is removed from the reference set.
// * An _indexed representation_ corresponding to an entry _not present_ in the reference set
// entails the following actions:
// * If referencing an element of the static table:
// * The header field corresponding to the referenced entry is emitted
// * The referenced static entry is added to the header table
// * A reference to this new header table entry is added to the reference set (except if
// this new entry didn't fit in the header table)
// * If referencing an element of the header table:
// * The header field corresponding to the referenced entry is emitted
// * The referenced header table entry is added to the reference set
// * An _indexed representation_ entails the following actions:
// * The header field corresponding to the referenced entry is emitted
else if (typeof rep.value === 'number') {
var index = rep.value;
entry = this._table[index];
if (entry.reference) {
entry.reference = false;
}
else {
pair = entry.slice();
this.push(pair);
if (index >= this._table.length - this._table._staticLength) {
entry = entryFromPair(pair);
this._table.add(entry);
}
entry.reference = true;
entry.emitted = true;
}
pair = entry.slice();
this.push(pair);
}
// * A _literal representation_ that is _not added_ to the header table entails the following
@ -324,7 +261,7 @@ HeaderSetDecompressor.prototype._execute = function _execute(rep) {
// * A _literal representation_ that is _added_ to the header table entails the following further
// actions:
// * The header is added to the header table.
// * The new entry is added to the reference set.
// * The header is emitted.
else {
if (typeof rep.name === 'number') {
pair = [this._table[rep.name][0], rep.value];
@ -334,8 +271,6 @@ HeaderSetDecompressor.prototype._execute = function _execute(rep) {
if (rep.index) {
entry = entryFromPair(pair);
entry.reference = true;
entry.emitted = true;
this._table.add(entry);
}
@ -356,15 +291,6 @@ HeaderSetDecompressor.prototype._flush = function _flush(callback) {
this._execute(HeaderSetDecompressor.header(buffer));
}
// * [emits the reference set](http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.2.2)
for (var index = 0; index < this._table.length; index++) {
var entry = this._table[index];
if (entry.reference && !entry.emitted) {
this.push(entry.slice());
}
entry.emitted = false;
}
callback();
};
@ -421,83 +347,22 @@ HeaderSetCompressor.prototype._transform = function _transform(pair, encoding, c
}
}
var mustNeverIndex = (name === 'cookie' || name === 'set-cookie' || name === 'authorization');
var mustNeverIndex = ((name === 'cookie' && value.length < 20) ||
(name === 'set-cookie' && value.length < 20) ||
name === 'authorization');
// * 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:
//
// * If the entry is outside the reference set, then a single indexed representation puts the
// entry into it and emits the header. Note that if the matched entry is in the static table,
// then it has to be added to the header table.
//
// * 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 && !mustNeverIndex) {
rep = { name: fullMatch, value: fullMatch, index: false };
if (!entry.reference) {
if (fullMatch >= this._table.length - this._table._staticLength) {
entry = entryFromPair(pair);
this._table.add(entry);
}
this.send(rep);
entry.reference = true;
entry.emitted = true;
}
else if (entry.keep) {
this.send(rep);
this.send(rep);
this.send(rep);
this.send(rep);
entry.keep = false;
entry.emitted = true;
}
else if (entry.emitted) {
this.send(rep);
this.send(rep);
}
else {
entry.keep = true;
}
this.send({ name: fullMatch, value: fullMatch, index: false });
}
// * otherwise, it will be a literal representation (with a name index if there's a name match)
else {
entry = entryFromPair(pair);
entry.emitted = true;
var indexing = (entry._size < this._table._limit / 2) && !mustNeverIndex;
if (indexing) {
entry.reference = true;
var droppedEntries = this._table.add(entry);
for (droppedIndex in droppedEntries) {
droppedIndex = Number(droppedIndex)
var dropped = droppedEntries[droppedIndex];
if (dropped.keep) {
rep = { name: droppedIndex, value: droppedIndex, index: false };
this.send(rep);
this.send(rep);
}
}
this._table.add(entry);
}
this.send({ name: (nameMatch !== -1) ? nameMatch : name, value: value, index: indexing, mustNeverIndex: mustNeverIndex, contextUpdate: false });
@ -510,17 +375,6 @@ HeaderSetCompressor.prototype._transform = function _transform(pair, encoding, c
// TransformStream class. It gets called when there's no more header to compress. The final step:
// [_flush]: http://nodejs.org/api/stream.html#stream_transform_flush_callback
HeaderSetCompressor.prototype._flush = function _flush(callback) {
// * removing entries from the header set that are not marked to be kept or emitted
for (var index = 0; index < this._table.length; index++) {
var entry = this._table[index];
if (entry.reference && !entry.keep && !entry.emitted) {
this.send({ name: index, value: index, index: false });
entry.reference = false;
}
entry.keep = false;
entry.emitted = false;
}
callback();
};
@ -1135,12 +989,7 @@ HeaderSetCompressor.header = function writeHeader(header) {
}
if (representation === representations.contextUpdate) {
if (header.clearReferenceSet) {
var buffer = new Buffer('10', 'hex');
buffers.push([buffer]);
} else {
buffers.push(HeaderSetCompressor.integer(header.newMaxSize, 4));
}
buffers.push(HeaderSetCompressor.integer(header.newMaxSize, 5));
}
else if (representation === representations.indexed) {
@ -1181,18 +1030,12 @@ HeaderSetDecompressor.header = function readHeader(buffer) {
header.value = header.name = -1;
header.index = false;
header.contextUpdate = false;
header.clearReferenceSet = false;
header.newMaxSize = 0;
header.mustNeverIndex = false;
if (representation === representations.contextUpdate) {
header.contextUpdate = true;
if (firstByte & 0x10) {
header.clearReferenceSet = true;
buffer.cursor += 1;
} else {
header.newMaxSize = HeaderSetDecompressor.integer(buffer, 4);
}
header.newMaxSize = HeaderSetDecompressor.integer(buffer, 5);
}
else if (representation === representations.indexed) {
@ -1234,7 +1077,7 @@ HeaderSetDecompressor.header = function readHeader(buffer) {
//
// There are possibly several binary frame that belong to a single non-binary frame.
var MAX_HTTP_PAYLOAD_SIZE = 16383;
var MAX_HTTP_PAYLOAD_SIZE = 16384;
// The Compressor class
// --------------------
@ -1260,7 +1103,19 @@ Compressor.prototype.setTableSizeLimit = function setTableSizeLimit(size) {
// but the API becomes simpler.
Compressor.prototype.compress = function compress(headers) {
var compressor = new HeaderSetCompressor(this._log, this._table);
var colonHeaders = [];
var nonColonHeaders = [];
// To ensure we send colon headers first
for (var name in headers) {
if (name.trim()[0] === ':') {
colonHeaders.push(name);
} else {
nonColonHeaders.push(name);
}
}
function compressHeader(name) {
var value = headers[name];
name = String(name).toLowerCase();
@ -1275,16 +1130,6 @@ Compressor.prototype.compress = function compress(headers) {
}));
}
// * To preserve the order of a comma-separated list, the ordered values for a single header
// field name appearing in different header fields are concatenated into a single value.
// A zero-valued octet (0x0) is used to delimit multiple values.
// * Header fields containing multiple values MUST be concatenated into a single value unless
// the ordering of that header field is known to be not significant.
// * Currently, only the Cookie header is considered to be order-insensitive.
if ((value instanceof Array) && (name !== 'cookie')) {
value = value.join('\0');
}
if (value instanceof Array) {
for (var i = 0; i < value.length; i++) {
compressor.write([name, String(value[i])]);
@ -1293,6 +1138,14 @@ Compressor.prototype.compress = function compress(headers) {
compressor.write([name, String(value)]);
}
}
for (var idx = 0; idx < colonHeaders.length; idx++) {
compressHeader(colonHeaders[idx]);
}
for (var idx = 0; idx < nonColonHeaders.length; idx++) {
compressHeader(nonColonHeaders[idx]);
}
compressor.end();
var chunk, chunks = [];
@ -1380,24 +1233,26 @@ Decompressor.prototype.decompress = function decompress(block) {
var decompressor = new HeaderSetDecompressor(this._log, this._table);
decompressor.end(block);
var seenNonColonHeader = false;
var headers = {};
var pair;
while (pair = decompressor.read()) {
var name = pair[0];
// * After decompression, header fields that have values containing zero octets (0x0) MUST be
// split into multiple header fields before being processed.
var values = pair[1].split('\0');
for (var i = 0; i < values.length; i++) {
var value = values[i];
if (name in headers) {
if (headers[name] instanceof Array) {
headers[name].push(value);
} else {
headers[name] = [headers[name], value];
}
var value = pair[1];
var isColonHeader = (name.trim()[0] === ':');
if (seenNonColonHeader && isColonHeader) {
this.emit('error', 'PROTOCOL_ERROR');
return headers;
}
seenNonColonHeader = !isColonHeader;
if (name in headers) {
if (headers[name] instanceof Array) {
headers[name].push(value);
} else {
headers[name] = value;
headers[name] = [headers[name], value];
}
} else {
headers[name] = value;
}
}

View File

@ -392,6 +392,7 @@ Connection.prototype._initializeSettingsManagement = function _initializeSetting
// * Forwarding SETTINGS frames to the `_receiveSettings` method
this.on('SETTINGS', this._receiveSettings);
this.on('RECEIVING_SETTINGS_MAX_FRAME_SIZE', this._sanityCheckMaxFrameSize);
};
// * Checking that the first frame the other endpoint sends is SETTINGS
@ -430,6 +431,13 @@ Connection.prototype._receiveSettings = function _receiveSettings(frame) {
}
};
Connection.prototype._sanityCheckMaxFrameSize = function _sanityCheckMaxFrameSize(value) {
if ((value < 0x4000) || (value >= 0x01000000)) {
this._log.fatal('Received invalid value for max frame size: ' + value);
this.emit('error');
}
};
// Changing one or more settings value and sending out a SETTINGS frame
Connection.prototype.set = function set(settings, callback) {
// * Calling the callback and emitting event when the change is acknowledges

View File

@ -167,8 +167,6 @@ function pipeAndFilter(stream1, stream2, filter) {
}
}
var MAX_HTTP_PAYLOAD_SIZE = 16383;
Endpoint.prototype._initializeDataFlow = function _initializeDataFlow(role, settings, filters) {
var firstStreamId, compressorRole, decompressorRole;
if (role === 'CLIENT') {
@ -181,8 +179,8 @@ Endpoint.prototype._initializeDataFlow = function _initializeDataFlow(role, sett
decompressorRole = 'REQUEST';
}
this._serializer = new Serializer(this._log, MAX_HTTP_PAYLOAD_SIZE);
this._deserializer = new Deserializer(this._log, MAX_HTTP_PAYLOAD_SIZE);
this._serializer = new Serializer(this._log);
this._deserializer = new Deserializer(this._log);
this._compressor = new Compressor(this._log, compressorRole);
this._decompressor = new Decompressor(this._log, decompressorRole);
this._connection = new Connection(this._log, firstStreamId, settings);
@ -193,9 +191,9 @@ Endpoint.prototype._initializeDataFlow = function _initializeDataFlow(role, sett
pipeAndFilter(this._decompressor, this._connection, filters.afterDecompression);
this._connection.on('ACKNOWLEDGED_SETTINGS_HEADER_TABLE_SIZE',
this._decompressor.setTableSizeLimit.bind(this._decompressor))
this._decompressor.setTableSizeLimit.bind(this._decompressor));
this._connection.on('RECEIVING_SETTINGS_HEADER_TABLE_SIZE',
this._compressor.setTableSizeLimit.bind(this._compressor))
this._compressor.setTableSizeLimit.bind(this._compressor));
};
var noread = {};

View File

@ -19,7 +19,7 @@ exports.Flow = Flow;
// * **setInitialWindow(size)**: the initial flow control window size can be changed *any time*
// ([as described in the standard][1]) using this method
//
// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.9.2
// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.9.2
// API for child classes
// ---------------------

View File

@ -11,7 +11,7 @@ exports.Deserializer = Deserializer;
var logData = Boolean(process.env.HTTP2_LOG_DATA);
var MAX_PAYLOAD_SIZE = 16383;
var MAX_PAYLOAD_SIZE = 16384;
var WINDOW_UPDATE_PAYLOAD_SIZE = 4;
// Serializer
@ -25,9 +25,8 @@ var WINDOW_UPDATE_PAYLOAD_SIZE = 4;
// empty adds payload adds header
// array buffers buffer
function Serializer(log, sizeLimit) {
function Serializer(log) {
this._log = log.child({ component: 'serializer' });
this._sizeLimit = sizeLimit || MAX_PAYLOAD_SIZE;
Transform.call(this, { objectMode: true });
}
Serializer.prototype = Object.create(Transform.prototype, { constructor: { value: Serializer } });
@ -42,9 +41,9 @@ Serializer.prototype._transform = function _transform(frame, encoding, done) {
var buffers = [];
Serializer[frame.type](frame, buffers);
Serializer.commonHeader(frame, buffers);
var length = Serializer.commonHeader(frame, buffers);
assert(buffers[0].readUInt16BE(0) <= this._sizeLimit, 'Frame too large!');
assert(length <= MAX_PAYLOAD_SIZE, 'Frame too large!');
for (var i = 0; i < buffers.length; i++) {
if (logData) {
@ -67,10 +66,9 @@ Serializer.prototype._transform = function _transform(frame, encoding, done) {
// empty adds parsed adds parsed
// object header properties payload properties
function Deserializer(log, sizeLimit, role) {
function Deserializer(log, role) {
this._role = role;
this._log = log.child({ component: 'deserializer' });
this._sizeLimit = sizeLimit || MAX_PAYLOAD_SIZE;
Transform.call(this, { objectMode: true });
this._next(COMMON_HEADER_SIZE);
}
@ -114,7 +112,7 @@ Deserializer.prototype._transform = function _transform(chunk, encoding, done) {
// deserializer waits for the specified length payload.
if ((this._cursor === this._buffer.length) && this._waitingForHeader) {
var payloadSize = Deserializer.commonHeader(this._buffer, this._frame);
if (payloadSize <= this._sizeLimit) {
if (payloadSize <= MAX_PAYLOAD_SIZE) {
this._next(payloadSize);
} else {
this.emit('error', 'FRAME_SIZE_ERROR');
@ -148,20 +146,22 @@ Deserializer.prototype._transform = function _transform(chunk, encoding, done) {
done();
};
// [Frame Header](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-4.1)
// [Frame Header](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-4.1)
// --------------------------------------------------------------
//
// HTTP/2.0 frames share a common base format consisting of an 8-byte header followed by 0 to 65535
// HTTP/2.0 frames share a common base format consisting of a 9-byte header followed by 0 to 2^24 - 1
// bytes of data.
//
// Additional size limits can be set by specific application uses. HTTP limits the frame size to
// 16,383 octets.
// 16,384 octets by default, though this can be increased by a receiver.
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | R | Length (14) | Type (8) | Flags (8) |
// +-+-+---------------------------+---------------+---------------+
// | Length (24) |
// +---------------+---------------+---------------+
// | Type (8) | Flags (8) |
// +-+-----------------------------+---------------+---------------+
// |R| Stream Identifier (31) |
// +-+-------------------------------------------------------------+
// | Frame Data (0...) ...
@ -169,12 +169,8 @@ Deserializer.prototype._transform = function _transform(chunk, encoding, done) {
//
// The fields of the frame header are defined as:
//
// * R:
// A reserved 2-bit field. The semantics of these bits are undefined and the bits MUST remain
// unset (0) when sending and MUST be ignored when receiving.
//
// * Length:
// The length of the frame data expressed as an unsigned 14-bit integer. The 8 bytes of the frame
// The length of the frame data expressed as an unsigned 24-bit integer. The 9 bytes of the frame
// header are not included in this value.
//
// * Type:
@ -197,7 +193,7 @@ Deserializer.prototype._transform = function _transform(chunk, encoding, done) {
//
// The structure and content of the remaining frame data is dependent entirely on the frame type.
var COMMON_HEADER_SIZE = 8;
var COMMON_HEADER_SIZE = 9;
var frameTypes = [];
@ -214,10 +210,11 @@ Serializer.commonHeader = function writeCommonHeader(frame, buffers) {
for (var i = 0; i < buffers.length; i++) {
size += buffers[i].length;
}
headerBuffer.writeUInt16BE(size, 0);
headerBuffer.writeUInt8(0, 0);
headerBuffer.writeUInt16BE(size, 1);
var typeId = frameTypes.indexOf(frame.type); // If we are here then the type is valid for sure
headerBuffer.writeUInt8(typeId, 2);
headerBuffer.writeUInt8(typeId, 3);
var flagByte = 0;
for (var flag in frame.flags) {
@ -227,31 +224,37 @@ Serializer.commonHeader = function writeCommonHeader(frame, buffers) {
flagByte |= (1 << position);
}
}
headerBuffer.writeUInt8(flagByte, 3);
headerBuffer.writeUInt8(flagByte, 4);
assert((0 <= frame.stream) && (frame.stream < 0x7fffffff), frame.stream);
headerBuffer.writeUInt32BE(frame.stream || 0, 4);
headerBuffer.writeUInt32BE(frame.stream || 0, 5);
buffers.unshift(headerBuffer);
return size;
};
Deserializer.commonHeader = function readCommonHeader(buffer, frame) {
var length = buffer.readUInt16BE(0);
var totallyWastedByte = buffer.readUInt8(0);
var length = buffer.readUInt16BE(1);
// We do this just for sanity checking later on, to make sure no one sent us a
// frame that's super large.
length += totallyWastedByte << 16;
frame.type = frameTypes[buffer.readUInt8(2)];
frame.type = frameTypes[buffer.readUInt8(3)];
if (!frame.type) {
// We are required to ignore unknown frame types
return length;
}
frame.flags = {};
var flagByte = buffer.readUInt8(3);
var flagByte = buffer.readUInt8(4);
var definedFlags = frameFlags[frame.type];
for (var i = 0; i < definedFlags.length; i++) {
frame.flags[definedFlags[i]] = Boolean(flagByte & (1 << i));
}
frame.stream = buffer.readUInt32BE(4) & 0x7fffffff;
frame.stream = buffer.readUInt32BE(5) & 0x7fffffff;
return length;
};
@ -266,7 +269,7 @@ Deserializer.commonHeader = function readCommonHeader(buffer, frame) {
// * `typeSpecificAttributes`: a register of frame specific frame object attributes (used by
// logging code and also serves as documentation for frame objects)
// [DATA Frames](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.1)
// [DATA Frames](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.1)
// ------------------------------------------------------------
//
// DATA frames (type=0x0) convey arbitrary, variable-length sequences of octets associated with a
@ -277,16 +280,12 @@ Deserializer.commonHeader = function readCommonHeader(buffer, frame) {
// * END_STREAM (0x1):
// Bit 1 being set indicates that this frame is the last that the endpoint will send for the
// identified stream.
// * END_SEGMENT (0x2):
// Bit 2 being set indicates that this frame is the last for the current segment. Intermediaries
// MUST NOT coalesce frames across a segment boundary and MUST preserve segment boundaries when
// forwarding frames.
// * PADDED (0x08):
// Bit 4 being set indicates that the Pad Length field is present.
frameTypes[0x0] = 'DATA';
frameFlags.DATA = ['END_STREAM', 'END_SEGMENT', 'RESERVED4', 'PADDED'];
frameFlags.DATA = ['END_STREAM', 'RESERVED2', 'RESERVED4', 'PADDED'];
typeSpecificAttributes.DATA = ['data'];
@ -309,7 +308,7 @@ Deserializer.DATA = function readData(buffer, frame) {
}
};
// [HEADERS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.2)
// [HEADERS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.2)
// --------------------------------------------------------------
//
// The HEADERS frame (type=0x1) allows the sender to create a stream.
@ -319,10 +318,6 @@ Deserializer.DATA = function readData(buffer, frame) {
// * END_STREAM (0x1):
// Bit 1 being set indicates that this frame is the last that the endpoint will send for the
// identified stream.
// * END_SEGMENT (0x2):
// Bit 2 being set indicates that this frame is the last for the current segment. Intermediaries
// MUST NOT coalesce frames across a segment boundary and MUST preserve segment boundaries when
// forwarding frames.
// * END_HEADERS (0x4):
// The END_HEADERS bit indicates that this frame contains the entire payload necessary to provide
// a complete set of headers.
@ -334,7 +329,7 @@ Deserializer.DATA = function readData(buffer, frame) {
frameTypes[0x1] = 'HEADERS';
frameFlags.HEADERS = ['END_STREAM', 'END_SEGMENT', 'END_HEADERS', 'PADDED', 'RESERVED5', 'PRIORITY'];
frameFlags.HEADERS = ['END_STREAM', 'RESERVED2', 'END_HEADERS', 'PADDED', 'RESERVED5', 'PRIORITY'];
typeSpecificAttributes.HEADERS = ['priorityDependency', 'priorityWeight', 'exclusiveDependency', 'headers', 'data'];
@ -395,7 +390,7 @@ Deserializer.HEADERS = function readHeadersPriority(buffer, frame) {
}
};
// [PRIORITY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.3)
// [PRIORITY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.3)
// -------------------------------------------------------
//
// The PRIORITY frame (type=0x2) specifies the sender-advised priority of a stream.
@ -440,7 +435,7 @@ Deserializer.PRIORITY = function readPriority(buffer, frame) {
frame.priorityWeight = buffer.readUInt8(4);
};
// [RST_STREAM](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.4)
// [RST_STREAM](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.4)
// -----------------------------------------------------------
//
// The RST_STREAM frame (type=0x3) allows for abnormal termination of a stream.
@ -478,7 +473,7 @@ Deserializer.RST_STREAM = function readRstStream(buffer, frame) {
}
};
// [SETTINGS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.5)
// [SETTINGS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.5)
// -------------------------------------------------------
//
// The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints
@ -581,7 +576,11 @@ definedSettings[3] = { name: 'SETTINGS_MAX_CONCURRENT_STREAMS', flag: false };
// indicates the sender's initial stream window size (in bytes) for new streams.
definedSettings[4] = { name: 'SETTINGS_INITIAL_WINDOW_SIZE', flag: false };
// [PUSH_PROMISE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.6)
// * SETTINGS_MAX_FRAME_SIZE (5):
// indicates the maximum size of a frame the receiver will allow.
definedSettings[5] = { name: 'SETTINGS_MAX_FRAME_SIZE', flag: false };
// [PUSH_PROMISE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.6)
// ---------------------------------------------------------------
//
// The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of streams the
@ -642,7 +641,7 @@ Deserializer.PUSH_PROMISE = function readPushPromise(buffer, frame) {
}
};
// [PING](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.7)
// [PING](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.7)
// -----------------------------------------------
//
// The PING frame (type=0x6) is a mechanism for measuring a minimal round-trip time from the
@ -672,7 +671,7 @@ Deserializer.PING = function readPing(buffer, frame) {
frame.data = buffer;
};
// [GOAWAY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.8)
// [GOAWAY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.8)
// ---------------------------------------------------
//
// The GOAWAY frame (type=0x7) informs the remote peer to stop creating streams on this connection.
@ -723,7 +722,7 @@ Deserializer.GOAWAY = function readGoaway(buffer, frame) {
}
};
// [WINDOW_UPDATE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.9)
// [WINDOW_UPDATE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.9)
// -----------------------------------------------------------------
//
// The WINDOW_UPDATE frame (type=0x8) is used to implement flow control.
@ -745,7 +744,7 @@ Serializer.WINDOW_UPDATE = function writeWindowUpdate(frame, buffers) {
var buffer = new Buffer(4);
var window_size = frame.window_size;
assert((0 <= window_size) && (window_size <= 0x7fffffff), window_size);
assert((0 < window_size) && (window_size <= 0x7fffffff), window_size);
buffer.writeUInt32BE(window_size, 0);
buffers.push(buffer);
@ -756,9 +755,12 @@ Deserializer.WINDOW_UPDATE = function readWindowUpdate(buffer, frame) {
return 'FRAME_SIZE_ERROR';
}
frame.window_size = buffer.readUInt32BE(0) & 0x7fffffff;
if (frame.window_size === 0) {
return 'PROTOCOL_ERROR';
}
};
// [CONTINUATION](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.10)
// [CONTINUATION](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.10)
// ------------------------------------------------------------
//
// The CONTINUATION frame (type=0x9) is used to continue a sequence of header block fragments.
@ -783,7 +785,7 @@ Deserializer.CONTINUATION = function readContinuation(buffer, frame) {
frame.data = buffer;
};
// [ALTSVC](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.11)
// [ALTSVC](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.11)
// ------------------------------------------------------------
//
// The ALTSVC frame (type=0xA) advertises the availability of an alternative service to the client.
@ -872,7 +874,7 @@ Deserializer.ALTSVC = function readAltSvc(buffer, frame) {
frame.origin = buffer.toString('ascii', 9 + pidLength + hostLength);
};
// [BLOCKED](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.12)
// [BLOCKED](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.12)
// ------------------------------------------------------------
//
// The BLOCKED frame (type=0xB) indicates that the sender is unable to send data
@ -892,7 +894,7 @@ Serializer.BLOCKED = function writeBlocked(frame, buffers) {
Deserializer.BLOCKED = function readBlocked(buffer, frame) {
};
// [Error Codes](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-7)
// [Error Codes](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-7)
// ------------------------------------------------------------
var errorCodes = [

View File

@ -1,4 +1,4 @@
// [node-http2-protocol][homepage] is an implementation of the [HTTP/2 (draft 13)][http2]
// [node-http2-protocol][homepage] is an implementation of the [HTTP/2 (draft 14)][http2]
// framing layer for [node.js][node].
//
// The main building blocks are [node.js streams][node-stream] that are connected through pipes.
@ -28,16 +28,16 @@
// between the binary and the JavaScript object representation of HTTP/2 frames
//
// [homepage]: https://github.com/molnarg/node-http2
// [http2]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-13
// [http2-connheader]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-3.5
// [http2-stream]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-5
// [http2-streamstate]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-5.1
// [http2]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14
// [http2-connheader]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-3.5
// [http2-stream]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5
// [http2-streamstate]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.1
// [node]: http://nodejs.org/
// [node-stream]: http://nodejs.org/api/stream.html
// [node-https]: http://nodejs.org/api/https.html
// [node-http]: http://nodejs.org/api/http.html
exports.ImplementedVersion = 'h2-13';
exports.ImplementedVersion = 'h2-14';
exports.Endpoint = require('./endpoint').Endpoint;

View File

@ -240,6 +240,10 @@ Stream.prototype._writeUpstream = function _writeUpstream(frame) {
// * If it's a control frame. Call the appropriate handler method.
if (frame.type === 'HEADERS') {
if (this._processedHeaders && !frame.flags['END_STREAM']) {
this.emit('error', 'PROTOCOL_ERROR');
}
this._processedHeaders = true;
this._onHeaders(frame);
} else if (frame.type === 'PUSH_PROMISE') {
this._onPromise(frame);
@ -346,7 +350,7 @@ Stream.prototype._finishing = function _finishing() {
}
};
// [Stream States](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-5.1)
// [Stream States](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.1)
// ----------------
//
// +--------+
@ -381,6 +385,7 @@ Stream.prototype._initializeState = function _initializeState() {
this._initiated = undefined;
this._closedByUs = undefined;
this._closedWithRst = undefined;
this._processedHeaders = false;
};
// Only `_setState` should change `this.state` directly. It also logs the state change and notifies

View File

@ -1,6 +1,6 @@
{
"name": "http2-protocol",
"version": "0.13.0",
"version": "0.14.1",
"description": "A JavaScript implementation of the HTTP/2 framing layer",
"main": "lib/index.js",
"engines" : {

View File

@ -76,149 +76,60 @@ ax-age=3600; version=1': '94e783f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7
};
var test_headers = [{
// literal w/index, name index
// index
header: {
name: 1,
value: 'GET',
index: true,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('42' + '03474554', 'hex')
}, {
// literal w/index, name index
header: {
name: 6,
value: 'http',
index: true,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('47' + '839d29af', 'hex')
}, {
// literal w/index, name index
header: {
name: 5,
value: '/',
index: true,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('46' + '012F', 'hex')
}, {
// literal w/index, name index
header: {
name: 3,
value: 'www.foo.com',
index: true,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('44' + '89f1e3c2f29ceb90f4ff', 'hex')
}, {
// literal w/index, name index
header: {
name: 2,
value: 'https',
index: true,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('43' + '849d29ad1f', 'hex')
}, {
// literal w/index, name index
header: {
name: 1,
value: 'www.bar.com',
index: true,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('42' + '89f1e3c2f18ec5c87a7f', 'hex')
}, {
// literal w/index, name index
header: {
name: 29,
value: 'no-cache',
index: true,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('5e' + '86a8eb10649cbf', 'hex')
}, {
// indexed
header: {
name: 3,
value: 3,
value: 1,
index: false,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('84', 'hex')
buffer: new Buffer('82', 'hex')
}, {
// indexed
// index
header: {
name: 5,
value: 5,
index: false,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('86', 'hex')
}, {
// literal w/index, name index
// index
header: {
name: 4,
value: '/custom-path.css',
index: true,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('45' + '8b6096a127a56ac699d72211', 'hex')
}, {
// literal w/index, new name & value
header: {
name: 'custom-key',
value: 'custom-value',
index: true,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('40' + '8825a849e95ba97d7f' + '8925a849e95bb8e8b4bf', 'hex')
}, {
// indexed
header: {
name: 2,
value: 2,
name: 3,
value: 3,
index: false,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('83', 'hex')
buffer: new Buffer('84', 'hex')
}, {
// literal w/index, name index
header: {
name: 0,
value: 'www.foo.com',
index: true,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('41' + '89f1e3c2f29ceb90f4ff', 'hex')
}, {
// indexed
header: {
name: 1,
value: 1,
index: false,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('82', 'hex')
}, {
// indexed
header: {
@ -227,10 +138,163 @@ var test_headers = [{
index: false,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('87', 'hex')
}, {
// indexed
header: {
name: 3,
value: 3,
index: false,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('84', 'hex')
}, {
// literal w/index, name index
header: {
name: 0,
value: 'www.bar.com',
index: true,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('41' + '89f1e3c2f18ec5c87a7f', 'hex')
}, {
// literal w/index, name index
header: {
name: 23,
value: 'no-cache',
index: true,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('58' + '86a8eb10649cbf', 'hex')
}, {
// index
header: {
name: 1,
value: 1,
index: false,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('82', 'hex')
}, {
// index
header: {
name: 6,
value: 6,
index: false,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('87', 'hex')
}, {
// literal w/index, name index
header: {
name: 3,
value: '/custom-path.css',
index: true,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('44' + '8b6096a127a56ac699d72211', 'hex')
}, {
// index
header: {
name: 63,
value: 63,
index: false,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('C0', 'hex')
}, {
// literal w/index, new name & value
header: {
name: 'custom-key',
value: 'custom-value',
index: true,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('40' + '8825a849e95ba97d7f' + '8925a849e95bb8e8b4bf', 'hex')
}, {
// index
header: {
name: 1,
value: 1,
index: false,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('82', 'hex')
}, {
// index
header: {
name: 6,
value: 6,
index: false,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('87', 'hex')
}, {
// index
header: {
name: 62,
value: 62,
index: false,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('BF', 'hex')
}, {
// index
header: {
name: 65,
value: 65,
index: false,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('C2', 'hex')
}, {
// index
header: {
name: 64,
value: 64,
index: false,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('C1', 'hex')
}, {
// index
header: {
name: 61,
value: 61,
index: false,
mustNeverIndex: false,
contextUpdate: false,
newMaxSize: 0
},
buffer: new Buffer('BE', 'hex')
}, {
// Literal w/o index, name index
header: {
@ -239,7 +303,6 @@ var test_headers = [{
index: false,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('07' + '86f138d25ee5b3', 'hex')
@ -251,7 +314,6 @@ var test_headers = [{
index: false,
mustNeverIndex: false,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('00' + '8294e7' + '03626172', 'hex')
@ -263,7 +325,6 @@ var test_headers = [{
index: false,
mustNeverIndex: true,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('17' + '86f138d25ee5b3', 'hex')
@ -275,7 +336,6 @@ var test_headers = [{
index: false,
mustNeverIndex: true,
contextUpdate: false,
clearReferenceSet: false,
newMaxSize: 0
},
buffer: new Buffer('10' + '8294e7' + '03626172', 'hex')
@ -286,21 +346,9 @@ var test_headers = [{
index: false,
mustNeverIndex: false,
contextUpdate: true,
clearReferenceSet: true,
newMaxSize: 0
},
buffer: new Buffer('30', 'hex')
}, {
header: {
name: -1,
value: -1,
index: false,
mustNeverIndex: false,
contextUpdate: true,
clearReferenceSet: false,
newMaxSize: 100
},
buffer: new Buffer('2F55', 'hex')
buffer: new Buffer('3F45', 'hex')
}];
var test_header_sets = [{
@ -328,7 +376,7 @@ var test_header_sets = [{
':authority': 'www.bar.com',
'custom-key': 'custom-value'
},
buffer: util.concat(test_headers.slice(9, 13).map(function(test) { return test.buffer; }))
buffer: util.concat(test_headers.slice(9, 14).map(function(test) { return test.buffer; }))
}, {
headers: {
':method': 'GET',
@ -337,15 +385,7 @@ var test_header_sets = [{
':authority': ['www.foo.com', 'www.bar.com'],
'custom-key': 'custom-value'
},
buffer: test_headers[3].buffer
}, {
headers: {
':status': '200',
'user-agent': 'my-user-agent',
'cookie': 'first; second; third; third; fourth',
'multiple': ['first', 'second', 'third', 'third; fourth'],
'verylong': (new Buffer(9000)).toString('hex')
}
buffer: util.concat(test_headers.slice(14, 19).map(function(test) { return test.buffer; }))
}];
describe('compressor.js', function() {

View File

@ -21,30 +21,30 @@ var frame_types = {
var test_frames = [{
frame: {
type: 'DATA',
flags: { END_STREAM: false, END_SEGMENT: false, RESERVED4: false,
flags: { END_STREAM: false, RESERVED2: false, RESERVED4: false,
PADDED: false },
stream: 10,
data: new Buffer('12345678', 'hex')
},
// length + type + flags + stream + content
buffer: new Buffer('0004' + '00' + '00' + '0000000A' + '12345678', 'hex')
buffer: new Buffer('000004' + '00' + '00' + '0000000A' + '12345678', 'hex')
}, {
frame: {
type: 'HEADERS',
flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
PADDED: false, RESERVED5: false, PRIORITY: false },
stream: 15,
data: new Buffer('12345678', 'hex')
},
buffer: new Buffer('0004' + '01' + '00' + '0000000F' + '12345678', 'hex')
buffer: new Buffer('000004' + '01' + '00' + '0000000F' + '12345678', 'hex')
}, {
frame: {
type: 'HEADERS',
flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
PADDED: false, RESERVED5: false, PRIORITY: true },
stream: 15,
priorityDependency: 10,
@ -53,13 +53,13 @@ var test_frames = [{
data: new Buffer('12345678', 'hex')
},
buffer: new Buffer('0009' + '01' + '20' + '0000000F' + '0000000A' + '05' + '12345678', 'hex')
buffer: new Buffer('000009' + '01' + '20' + '0000000F' + '0000000A' + '05' + '12345678', 'hex')
}, {
frame: {
type: 'HEADERS',
flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
PADDED: false, RESERVED5: false, PRIORITY: true },
stream: 15,
priorityDependency: 10,
@ -68,7 +68,7 @@ var test_frames = [{
data: new Buffer('12345678', 'hex')
},
buffer: new Buffer('0009' + '01' + '20' + '0000000F' + '8000000A' + '05' + '12345678', 'hex')
buffer: new Buffer('000009' + '01' + '20' + '0000000F' + '8000000A' + '05' + '12345678', 'hex')
}, {
frame: {
@ -80,7 +80,7 @@ var test_frames = [{
priorityWeight: 5,
exclusiveDependency: false
},
buffer: new Buffer('0005' + '02' + '00' + '0000000A' + '00000009' + '05', 'hex')
buffer: new Buffer('000005' + '02' + '00' + '0000000A' + '00000009' + '05', 'hex')
}, {
frame: {
@ -92,7 +92,7 @@ var test_frames = [{
priorityWeight: 5,
exclusiveDependency: true
},
buffer: new Buffer('0005' + '02' + '00' + '0000000A' + '80000009' + '05', 'hex')
buffer: new Buffer('000005' + '02' + '00' + '0000000A' + '80000009' + '05', 'hex')
}, {
frame: {
@ -102,7 +102,7 @@ var test_frames = [{
error: 'INTERNAL_ERROR'
},
buffer: new Buffer('0004' + '03' + '00' + '0000000A' + '00000002', 'hex')
buffer: new Buffer('000004' + '03' + '00' + '0000000A' + '00000002', 'hex')
}, {
frame: {
@ -114,13 +114,15 @@ var test_frames = [{
SETTINGS_HEADER_TABLE_SIZE: 0x12345678,
SETTINGS_ENABLE_PUSH: true,
SETTINGS_MAX_CONCURRENT_STREAMS: 0x01234567,
SETTINGS_INITIAL_WINDOW_SIZE: 0x89ABCDEF
SETTINGS_INITIAL_WINDOW_SIZE: 0x89ABCDEF,
SETTINGS_MAX_FRAME_SIZE: 0x00010000
}
},
buffer: new Buffer('0018' + '04' + '00' + '0000000A' + '0001' + '12345678' +
'0002' + '00000001' +
'0003' + '01234567' +
'0004' + '89ABCDEF', 'hex')
buffer: new Buffer('00001E' + '04' + '00' + '0000000A' + '0001' + '12345678' +
'0002' + '00000001' +
'0003' + '01234567' +
'0004' + '89ABCDEF' +
'0005' + '00010000', 'hex')
}, {
frame: {
@ -132,7 +134,7 @@ var test_frames = [{
promised_stream: 3,
data: new Buffer('12345678', 'hex')
},
buffer: new Buffer('0008' + '05' + '00' + '0000000F' + '00000003' + '12345678', 'hex')
buffer: new Buffer('000008' + '05' + '00' + '0000000F' + '00000003' + '12345678', 'hex')
}, {
frame: {
@ -142,7 +144,7 @@ var test_frames = [{
data: new Buffer('1234567887654321', 'hex')
},
buffer: new Buffer('0008' + '06' + '00' + '0000000F' + '1234567887654321', 'hex')
buffer: new Buffer('000008' + '06' + '00' + '0000000F' + '1234567887654321', 'hex')
}, {
frame: {
@ -153,7 +155,7 @@ var test_frames = [{
last_stream: 0x12345678,
error: 'PROTOCOL_ERROR'
},
buffer: new Buffer('0008' + '07' + '00' + '0000000A' + '12345678' + '00000001', 'hex')
buffer: new Buffer('000008' + '07' + '00' + '0000000A' + '12345678' + '00000001', 'hex')
}, {
frame: {
@ -163,7 +165,7 @@ var test_frames = [{
window_size: 0x12345678
},
buffer: new Buffer('0004' + '08' + '00' + '0000000A' + '12345678', 'hex')
buffer: new Buffer('000004' + '08' + '00' + '0000000A' + '12345678', 'hex')
}, {
frame: {
type: 'CONTINUATION',
@ -173,7 +175,7 @@ var test_frames = [{
data: new Buffer('12345678', 'hex')
},
// length + type + flags + stream + content
buffer: new Buffer('0004' + '09' + '04' + '0000000A' + '12345678', 'hex')
buffer: new Buffer('000004' + '09' + '04' + '0000000A' + '12345678', 'hex')
}, {
frame: {
type: 'ALTSVC',
@ -186,7 +188,7 @@ var test_frames = [{
host: "altsvc.example.com",
origin: ""
},
buffer: new Buffer('001D' + '0A' + '00' + '00000000' + '01E13380' + '115B' + '00' + '02' + '6832' + '12' + '616C747376632E6578616D706C652E636F6D', 'hex')
buffer: new Buffer('00001D' + '0A' + '00' + '00000000' + '01E13380' + '115B' + '00' + '02' + '6832' + '12' + '616C747376632E6578616D706C652E636F6D', 'hex')
}, {
frame: {
type: 'ALTSVC',
@ -199,7 +201,7 @@ var test_frames = [{
host: "altsvc.example.com",
origin: "https://onlyme.example.com"
},
buffer: new Buffer('0037' + '0A' + '00' + '00000000' + '01E13380' + '115B' + '00' + '02' + '6832' + '12' + '616C747376632E6578616D706C652E636F6D' + '68747470733A2F2F6F6E6C796D652E6578616D706C652E636F6D', 'hex')
buffer: new Buffer('000037' + '0A' + '00' + '00000000' + '01E13380' + '115B' + '00' + '02' + '6832' + '12' + '616C747376632E6578616D706C652E636F6D' + '68747470733A2F2F6F6E6C796D652E6578616D706C652E636F6D', 'hex')
}, {
frame: {
@ -207,37 +209,37 @@ var test_frames = [{
flags: { },
stream: 10
},
buffer: new Buffer('0000' + '0B' + '00' + '0000000A', 'hex')
buffer: new Buffer('000000' + '0B' + '00' + '0000000A', 'hex')
}];
var deserializer_test_frames = test_frames.slice(0);
var padded_test_frames = [{
frame: {
type: 'DATA',
flags: { END_STREAM: false, END_SEGMENT: false, RESERVED4: false,
flags: { END_STREAM: false, RESERVED2: false, RESERVED4: false,
PADDED: true },
stream: 10,
data: new Buffer('12345678', 'hex')
},
// length + type + flags + stream + pad length + content + padding
buffer: new Buffer('000B' + '00' + '08' + '0000000A' + '06' + '12345678' + '000000000000', 'hex')
buffer: new Buffer('00000B' + '00' + '08' + '0000000A' + '06' + '12345678' + '000000000000', 'hex')
}, {
frame: {
type: 'HEADERS',
flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
PADDED: true, RESERVED5: false, PRIORITY: false },
stream: 15,
data: new Buffer('12345678', 'hex')
},
// length + type + flags + stream + pad length + data + padding
buffer: new Buffer('000B' + '01' + '08' + '0000000F' + '06' + '12345678' + '000000000000', 'hex')
buffer: new Buffer('00000B' + '01' + '08' + '0000000F' + '06' + '12345678' + '000000000000', 'hex')
}, {
frame: {
type: 'HEADERS',
flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
PADDED: true, RESERVED5: false, PRIORITY: true },
stream: 15,
priorityDependency: 10,
@ -247,12 +249,12 @@ var padded_test_frames = [{
data: new Buffer('12345678', 'hex')
},
// length + type + flags + stream + pad length + priority dependency + priority weight + data + padding
buffer: new Buffer('0010' + '01' + '28' + '0000000F' + '06' + '0000000A' + '05' + '12345678' + '000000000000', 'hex')
buffer: new Buffer('000010' + '01' + '28' + '0000000F' + '06' + '0000000A' + '05' + '12345678' + '000000000000', 'hex')
}, {
frame: {
type: 'HEADERS',
flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
PADDED: true, RESERVED5: false, PRIORITY: true },
stream: 15,
priorityDependency: 10,
@ -262,7 +264,7 @@ var padded_test_frames = [{
data: new Buffer('12345678', 'hex')
},
// length + type + flags + stream + pad length + priority dependency + priority weight + data + padding
buffer: new Buffer('0010' + '01' + '28' + '0000000F' + '06' + '8000000A' + '05' + '12345678' + '000000000000', 'hex')
buffer: new Buffer('000010' + '01' + '28' + '0000000F' + '06' + '8000000A' + '05' + '12345678' + '000000000000', 'hex')
}, {
frame: {
@ -275,7 +277,7 @@ var padded_test_frames = [{
data: new Buffer('12345678', 'hex')
},
// length + type + flags + stream + pad length + promised stream + data + padding
buffer: new Buffer('000F' + '05' + '08' + '0000000F' + '06' + '00000003' + '12345678' + '000000000000', 'hex')
buffer: new Buffer('00000F' + '05' + '08' + '0000000F' + '06' + '00000003' + '12345678' + '000000000000', 'hex')
}];
for (var idx = 0; idx < padded_test_frames.length; idx++) {
@ -286,11 +288,11 @@ for (var idx = 0; idx < padded_test_frames.length; idx++) {
describe('framer.js', function() {
describe('Serializer', function() {
describe('static method .commonHeader({ type, flags, stream }, buffer_array)', function() {
it('should add the appropriate 8 byte header buffer in front of the others', function() {
it('should add the appropriate 9 byte header buffer in front of the others', function() {
for (var i = 0; i < test_frames.length; i++) {
var test = test_frames[i];
var buffers = [test.buffer.slice(8)];
var header_buffer = test.buffer.slice(0,8);
var buffers = [test.buffer.slice(9)];
var header_buffer = test.buffer.slice(0,9);
Serializer.commonHeader(test.frame, buffers);
expect(buffers[0]).to.deep.equal(header_buffer);
}
@ -306,7 +308,7 @@ describe('framer.js', function() {
var test = tests[i];
var buffers = [];
Serializer[type](test.frame, buffers);
expect(util.concat(buffers)).to.deep.equal(test.buffer.slice(8));
expect(util.concat(buffers)).to.deep.equal(test.buffer.slice(9));
}
});
});
@ -334,7 +336,7 @@ describe('framer.js', function() {
it('should augment the frame object with these properties: { type, flags, stream })', function() {
for (var i = 0; i < deserializer_test_frames.length; i++) {
var test = deserializer_test_frames[i], frame = {};
Deserializer.commonHeader(test.buffer.slice(0,8), frame);
Deserializer.commonHeader(test.buffer.slice(0,9), frame);
expect(frame).to.deep.equal({
type: test.frame.type,
flags: test.frame.flags,
@ -356,7 +358,7 @@ describe('framer.js', function() {
flags: test.frame.flags,
stream: test.frame.stream
};
Deserializer[type](test.buffer.slice(8), frame);
Deserializer[type](test.buffer.slice(9), frame);
expect(frame).to.deep.equal(test.frame);
}
});

View File

@ -1,13 +1,13 @@
{
"name": "http2",
"version": "2.6.0",
"version": "2.7.1",
"description": "An HTTP/2 client and server implementation",
"main": "lib/index.js",
"engines" : {
"node" : ">=0.10.19"
},
"dependencies": {
"http2-protocol": ">=0.13.0"
"http2-protocol": ">=0.14.1"
},
"devDependencies": {
"istanbul": "*",

View File

@ -85,7 +85,7 @@ describe('http.js', function() {
request[name].apply(request, originalArguments);
var mockFallbackRequest = { on: util.noop };
mockFallbackRequest[name] = function() {
expect(arguments).to.deep.equal(originalArguments);
expect(Array.prototype.slice.call(arguments)).to.deep.equal(originalArguments);
done();
};
request._fallback(mockFallbackRequest);
@ -111,6 +111,22 @@ describe('http.js', function() {
});
});
});
describe('OutgoingResponse', function() {
it('should throw error when writeHead is called multiple times on it', function() {
var called = false;
var stream = { _log: util.log, headers: function () {
if (called) {
throw new Error('Should not send headers twice');
} else {
called = true;
}
}, once: util.noop };
var response = new http2.OutgoingResponse(stream);
response.writeHead(200);
response.writeHead(404)
});
});
describe('test scenario', function() {
describe('simple request', function() {
it('should work as expected', function(done) {