bug 1124717 - 2/4 update CI to node-http2 3.1.0 to fix flow control issue r=hurley
authorPatrick McManus <mcmanus@ducksong.com>
Mon, 26 Jan 2015 20:21:54 -0500
changeset 240713 edee665f5e632ae73256b23307857db7d0b8b770
parent 240712 7b7d437f2adfe195bbf1c46fcd91fb47927e3893
child 240714 141093a1f646ab15b24619b384b60d6c484b0133
push idunknown
push userunknown
push dateunknown
reviewershurley
bugs1124717
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1124717 - 2/4 update CI to node-http2 3.1.0 to fix flow control issue r=hurley a test for 1124717 ran into https://github.com/molnarg/node-http2/issues/89 which is fixed in this update
testing/xpcshell/node-http2/HISTORY.md
testing/xpcshell/node-http2/README.md
testing/xpcshell/node-http2/example/client.js
testing/xpcshell/node-http2/example/server.js
testing/xpcshell/node-http2/lib/http.js
testing/xpcshell/node-http2/lib/index.js
testing/xpcshell/node-http2/lib/protocol/compressor.js
testing/xpcshell/node-http2/lib/protocol/flow.js
testing/xpcshell/node-http2/lib/protocol/framer.js
testing/xpcshell/node-http2/lib/protocol/index.js
testing/xpcshell/node-http2/lib/protocol/stream.js
testing/xpcshell/node-http2/package.json
testing/xpcshell/node-http2/test/compressor.js
testing/xpcshell/node-http2/test/connection.js
testing/xpcshell/node-http2/test/flow.js
testing/xpcshell/node-http2/test/http.js
testing/xpcshell/node-http2/test/stream.js
testing/xpcshell/node-http2/test/util.js
--- a/testing/xpcshell/node-http2/HISTORY.md
+++ b/testing/xpcshell/node-http2/HISTORY.md
@@ -1,12 +1,25 @@
 Version history
 ===============
 
-### 3.0.0 (2014-08-XX) ###
+### 3.1.0 (2014-12-11) ###
+
+* Upgrade to the latest draft: [draft-ietf-httpbis-http2-16]
+ * This involves some state transition changes that are technically incompatible with draft-14. If you need to be assured to interop on -14, continue using 3.0.1
+
+[draft-ietf-httpbis-http2-16]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-16
+
+### 3.0.1 (2014-11-20) ###
+
+* Bugfix release.
+* Fixed #81 and #87
+* Fixed a bug in flow control (without GitHub issue)
+
+### 3.0.0 (2014-08-25) ###
 
 * Re-join node-http2 and node-http2-protocol into one repository
 * API Changes
  * The default versions of createServer, request, and get now enforce TLS-only
  * The raw versions of createServer, request, and get are now under http2.raw instead of http2
  * What was previously in the http2-protocol repository/module is now available under http2.protocol from this repo/module
  * http2-protocol.ImplementedVersion is now http2.protocol.VERSION (the ALPN token)
 
--- a/testing/xpcshell/node-http2/README.md
+++ b/testing/xpcshell/node-http2/README.md
@@ -1,12 +1,12 @@
 node-http2
 ==========
 
-An HTTP/2 ([draft-ietf-httpbis-http2-14](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14))
+An HTTP/2 ([draft-ietf-httpbis-http2-16](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16))
 client and server implementation for node.js.
 
 ![Travis CI status](https://travis-ci.org/molnarg/node-http2.svg?branch=master)
 
 Installation
 ------------
 
 ```
@@ -111,22 +111,22 @@ The developer documentation is generated
 ### Running the tests ###
 
 It's easy, just run `npm test`. The tests are written in BDD style, so they are a good starting
 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 3.0.0:
+Code coverage summary as of version 3.0.1:
 ```
-Statements   : 91.85% ( 1747/1902 )
-Branches     : 81.61% ( 688/843 )
-Functions    : 90.95% ( 211/232 )
-Lines        : 91.92% ( 1741/1894 )
+Statements   : 92.09% ( 1759/1910 )
+Branches     : 82.56% ( 696/843 )
+Functions    : 91.38% ( 212/232 )
+Lines        : 92.17% ( 1753/1902 )
 ```
 
 There's a hosted version of the detailed (line-by-line) coverage report
 [here](http://molnarg.github.io/node-http2/coverage/lcov-report/lib/).
 
 ### Logging ###
 
 Logging is turned off by default. You can turn it on by passing a bunyan logger as `log` option when
@@ -146,22 +146,27 @@ Running the example server and client wi
 
 ```bash
 $ HTTP2_LOG=info node ./example/client.js 'http://localhost:8080/server.js' >/dev/null
 ```
 
 Contributors
 ------------
 
+The co-maintainer of the project is [Nick Hurley](https://github.com/todesschaf).
+
 Code contributions are always welcome! People who contributed to node-http2 so far:
 
-* Nick Hurley
-* Mike Belshe
-* Yoshihiro Iwanaga
-* vsemogutor
+* [Nick Hurley](https://github.com/todesschaf)
+* [Mike Belshe](https://github.com/mbelshe)
+* [Yoshihiro Iwanaga](https://github.com/iwanaga)
+* [Igor Novikov](https://github.com/vsemogutor)
+* [James Willcox](https://github.com/snorp)
+* [David Björklund](https://github.com/kesla)
+* [Patrick McManus](https://github.com/mcmanus)
 
 Special thanks to Google for financing the development of this module as part of their [Summer of
 Code program](https://developers.google.com/open-source/soc/) (project: [HTTP/2 prototype server
 implementation](https://google-melange.appspot.com/gsoc/project/google/gsoc2013/molnarg/5001)), and
 Nick Hurley of Mozilla, my GSoC mentor, who helped with regular code review and technical advices.
 
 License
 -------
--- a/testing/xpcshell/node-http2/example/client.js
+++ b/testing/xpcshell/node-http2/example/client.js
@@ -1,24 +1,23 @@
 var fs = require('fs');
 var path = require('path');
 var http2 = require('..');
 
+// Setting the global logger (optional)
 http2.globalAgent = new http2.Agent({
   log: require('../test/util').createLogger('client')
 });
 
+// We use self signed certs in the example code so we ignore cert errors
 process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
 
 // Sending the request
-// It would be `var request = http2.get(process.argv.pop());` if we wouldn't care about plain mode
-var options = require('url').parse(process.argv.pop());
-options.plain = Boolean(process.env.HTTP2_PLAIN);
-var request = http2.request(options);
-request.end();
+var url = process.argv.pop();
+var request = process.env.HTTP2_PLAIN ? http2.raw.get(url) : http2.get(url);
 
 // Receiving the response
 request.on('response', function(response) {
   response.pipe(process.stdout);
   response.on('end', finish);
 });
 
 // Receiving push streams
--- a/testing/xpcshell/node-http2/example/server.js
+++ b/testing/xpcshell/node-http2/example/server.js
@@ -1,28 +1,18 @@
 var fs = require('fs');
 var path = require('path');
 var http2 = require('..');
 
-var options = process.env.HTTP2_PLAIN ? {
-  plain: true
-} : {
-  key: fs.readFileSync(path.join(__dirname, '/localhost.key')),
-  cert: fs.readFileSync(path.join(__dirname, '/localhost.crt'))
-};
-
-// Passing bunyan logger (optional)
-options.log = require('../test/util').createLogger('server');
-
 // We cache one file to be able to do simple performance tests without waiting for the disk
 var cachedFile = fs.readFileSync(path.join(__dirname, './server.js'));
 var cachedUrl = '/server.js';
 
-// Creating the server
-var server = http2.createServer(options, function(request, response) {
+// The callback to handle requests
+function onRequest(request, response) {
   var filename = path.join(__dirname, request.url);
 
   // Serving server.js from cache. Useful for microbenchmarks.
   if (request.url === cachedUrl) {
     response.end(cachedFile);
   }
 
   // Reading file from disk if it exists and is safe.
@@ -39,11 +29,27 @@ var server = http2.createServer(options,
     fs.createReadStream(filename).pipe(response);
   }
 
   // Otherwise responding with 404.
   else {
     response.writeHead('404');
     response.end();
   }
-});
+}
+
+// Creating a bunyan logger (optional)
+var log = require('../test/util').createLogger('server');
 
+// Creating the server in plain or TLS mode (TLS mode is the default)
+var server;
+if (process.env.HTTP2_PLAIN) {
+  server = http2.raw.createServer({
+    log: log
+  }, onRequest);
+} else {
+  server = http2.createServer({
+    log: log,
+    key: fs.readFileSync(path.join(__dirname, '/localhost.key')),
+    cert: fs.readFileSync(path.join(__dirname, '/localhost.crt'))
+  }, onRequest);
+}
 server.listen(process.env.HTTP2_PORT || 8080);
--- a/testing/xpcshell/node-http2/lib/http.js
+++ b/testing/xpcshell/node-http2/lib/http.js
@@ -116,17 +116,17 @@
 //   - **request.setSocketKeepAlive([enable], [initialDelay])**
 //
 // - **Class: http2.IncomingMessage**
 //   - **Event: 'close'**
 //   - **message.setTimeout(timeout, [callback])**
 //
 // [1]: http://nodejs.org/api/https.html
 // [2]: http://nodejs.org/api/http.html
-// [3]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.2
+// [3]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.4
 // [expect-continue]: https://github.com/http2/http2-spec/issues/18
 // [connect]: https://github.com/http2/http2-spec/issues/230
 
 // Common server and client side code
 // ==================================
 
 var net = require('net');
 var url = require('url');
@@ -241,17 +241,17 @@ function IncomingMessage(stream) {
   this._lastHeadersSeen = undefined;
 
   // * Other metadata is filled in when the headers arrive.
   stream.once('headers', this._onHeaders.bind(this));
   stream.once('end', this._onEnd.bind(this));
 }
 IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } });
 
-// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.1)
+// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.3)
 // * `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.
 IncomingMessage.prototype._onHeaders = function _onHeaders(headers) {
   // * Detects malformed headers
   this._validateHeaders(headers);
 
   // * Store the _regular_ headers in `this.headers`
@@ -597,17 +597,17 @@ exports.http.createServer = exports.http
 // IncomingRequest class
 // ---------------------
 
 function IncomingRequest(stream) {
   IncomingMessage.call(this, stream);
 }
 IncomingRequest.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingRequest } });
 
-// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.1)
+// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.3)
 // * `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.
 IncomingRequest.prototype._onHeaders = function _onHeaders(headers) {
   // * The ":method" header field includes the HTTP method
   // * The ":scheme" header field includes the scheme portion of the target URI
   // * The ":authority" header field includes the authority portion of the target URI
   // * The ":path" header field includes the path and query parts of the target URI.
@@ -748,47 +748,51 @@ exports.OutgoingRequest = OutgoingReques
 exports.IncomingResponse = IncomingResponse;
 exports.Agent = Agent;
 exports.globalAgent = undefined;
 
 function requestRaw(options, callback) {
   if (typeof options === "string") {
     options = url.parse(options);
   }
-  if ((options.protocol && options.protocol !== "http:") || !options.plain) {
+  options.plain = true;
+  if (options.protocol && options.protocol !== "http:") {
     throw new Error('This interface only supports http-schemed URLs');
   }
   return (options.agent || exports.globalAgent).request(options, callback);
 }
 
 function requestTLS(options, callback) {
   if (typeof options === "string") {
     options = url.parse(options);
   }
-  if ((options.protocol && options.protocol !== "https:") || options.plain) {
+  options.plain = false;
+  if (options.protocol && options.protocol !== "https:") {
     throw new Error('This interface only supports https-schemed URLs');
   }
   return (options.agent || exports.globalAgent).request(options, callback);
 }
 
 function getRaw(options, callback) {
   if (typeof options === "string") {
     options = url.parse(options);
   }
-  if ((options.protocol && options.protocol !== "http:") || !options.plain) {
+  options.plain = true;
+  if (options.protocol && options.protocol !== "http:") {
     throw new Error('This interface only supports http-schemed URLs');
   }
   return (options.agent || exports.globalAgent).get(options, callback);
 }
 
 function getTLS(options, callback) {
   if (typeof options === "string") {
     options = url.parse(options);
   }
-  if ((options.protocol && options.protocol !== "https:") || options.plain) {
+  options.plain = false;
+  if (options.protocol && options.protocol !== "https:") {
     throw new Error('This interface only supports https-schemed URLs');
   }
   return (options.agent || exports.globalAgent).get(options, callback);
 }
 
 // Agent class
 // -----------
 
@@ -858,30 +862,30 @@ Agent.prototype.request = function reque
       host: options.host,
       port: options.port,
       localAddress: options.localAddress
     });
     endpoint.pipe(endpoint.socket).pipe(endpoint);
     request._start(endpoint.createStream(), options);
   }
 
-  // * HTTP/2 over TLS negotiated using NPN or ALPN
+  // * HTTP/2 over TLS negotiated using NPN or ALPN, or fallback to HTTPS1
   else {
     var started = false;
     options.ALPNProtocols = supportedProtocols;
     options.NPNProtocols = supportedProtocols;
     options.servername = options.host; // Server Name Indication
     options.agent = this._httpsAgent;
     options.ciphers = options.ciphers || cipherSuites;
     var httpsRequest = https.request(options);
 
     httpsRequest.on('socket', function(socket) {
       var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol;
       if (negotiatedProtocol != null) { // null in >=0.11.0, undefined in <0.11.0
-        negotiated()
+        negotiated();
       } else {
         socket.on('secureConnect', negotiated);
       }
     });
 
     var self = this;
     function negotiated() {
       var endpoint;
@@ -889,21 +893,22 @@ Agent.prototype.request = function reque
       if (negotiatedProtocol === protocol.VERSION) {
         httpsRequest.socket.emit('agentRemove');
         unbundleSocket(httpsRequest.socket);
         endpoint = new Endpoint(self._log, 'CLIENT', self._settings);
         endpoint.socket = httpsRequest.socket;
         endpoint.pipe(endpoint.socket).pipe(endpoint);
       }
       if (started) {
+        // ** In the meantime, an other connection was made to the same host...
         if (endpoint) {
+          // *** and it turned out to be HTTP2 and the request was multiplexed on that one, so we should close this one
           endpoint.close();
-        } else {
-          httpsRequest.abort();
         }
+        // *** otherwise, the fallback to HTTPS1 is already done.
       } else {
         if (endpoint) {
           self._log.info({ e: endpoint, server: options.host + ':' + options.port },
                          'New outgoing HTTP/2 connection');
           self.endpoints[key] = endpoint;
           self.emit(key, endpoint);
         } else {
           self.emit(key, undefined);
@@ -1074,17 +1079,17 @@ OutgoingRequest.prototype._onPromise = f
 // IncomingResponse class
 // ----------------------
 
 function IncomingResponse(stream) {
   IncomingMessage.call(this, stream);
 }
 IncomingResponse.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingResponse } });
 
-// [Response Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.2)
+// [Response Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.4)
 // * `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.
 IncomingResponse.prototype._onHeaders = function _onHeaders(headers) {
   // * A single ":status" header field is defined that carries the HTTP status code field. This
   //   header field MUST be included in all responses.
   // * A client MUST treat the absence of the ":status" header field, the presence of multiple
   //   values, or an invalid value as a stream error of type PROTOCOL_ERROR.
--- a/testing/xpcshell/node-http2/lib/index.js
+++ b/testing/xpcshell/node-http2/lib/index.js
@@ -1,21 +1,21 @@
-// [node-http2][homepage] is an [HTTP/2 (draft 14)][http2] implementation for [node.js][node].
+// [node-http2][homepage] is an [HTTP/2 (draft 16)][http2] implementation for [node.js][node].
 //
 // The core of the protocol is implemented in the protocol sub-directory. This directory provides
 // two important features on top of the protocol:
 //
 // * Implementation of different negotiation schemes that can be used to start a HTTP2 connection.
 //   These include TLS ALPN, Upgrade and Plain TCP.
 //
 // * Providing an API very similar to the standard node.js [HTTPS module API][node-https]
 //   (which is in turn very similar to the [HTTP module API][node-http]).
 //
 // [homepage]:            https://github.com/molnarg/node-http2
-// [http2]:               http://tools.ietf.org/html/draft-ietf-httpbis-http2-14
+// [http2]:               http://tools.ietf.org/html/draft-ietf-httpbis-http2-16
 // [node]:                http://nodejs.org/
 // [node-https]:          http://nodejs.org/api/https.html
 // [node-http]:           http://nodejs.org/api/http.html
 
 module.exports   = require('./http');
 
 /*
                   HTTP API
--- a/testing/xpcshell/node-http2/lib/protocol/compressor.js
+++ b/testing/xpcshell/node-http2/lib/protocol/compressor.js
@@ -1118,20 +1118,20 @@ Compressor.prototype.compress = function
   function compressHeader(name) {
     var value = headers[name];
     name = String(name).toLowerCase();
 
     // * To allow for better compression efficiency, the Cookie header field MAY be split into
     //   separate header fields, each with one or more cookie-pairs.
     if (name == 'cookie') {
       if (!(value instanceof Array)) {
-        value = [value]
+        value = [value];
       }
       value = Array.prototype.concat.apply([], value.map(function(cookie) {
-        return String(cookie).split(';').map(trim)
+        return String(cookie).split(';').map(trim);
       }));
     }
 
     if (value instanceof Array) {
       for (var i = 0; i < value.length; i++) {
         compressor.write([name, String(value[i])]);
       }
     } else {
@@ -1251,17 +1251,17 @@ Decompressor.prototype.decompress = func
       headers[name] = value;
     }
   }
 
   // * If there are multiple Cookie header fields after decompression, these MUST be concatenated
   //   into a single octet string using the two octet delimiter of 0x3B, 0x20 (the ASCII
   //   string "; ").
   if (('cookie' in headers) && (headers['cookie'] instanceof Array)) {
-    headers['cookie'] = headers['cookie'].join('; ')
+    headers['cookie'] = headers['cookie'].join('; ');
   }
 
   return headers;
 };
 
 // When a `frame` arrives
 Decompressor.prototype._transform = function _transform(frame, encoding, done) {
   // * and the collection process is already `_inProgress`, the frame is simply stored, except if
@@ -1335,10 +1335,10 @@ function cut(buffer, size) {
     var chunkSize = Math.min(size, buffer.length - cursor);
     chunks.push(buffer.slice(cursor, cursor + chunkSize));
     cursor += chunkSize;
   } while(cursor < buffer.length);
   return chunks;
 }
 
 function trim(string) {
-  return string.trim()
+  return string.trim();
 }
--- a/testing/xpcshell/node-http2/lib/protocol/flow.js
+++ b/testing/xpcshell/node-http2/lib/protocol/flow.js
@@ -14,17 +14,17 @@ exports.Flow = Flow;
 // Public API
 // ----------
 
 // * **Event: 'error' (type)**: signals an error
 //
 // * **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-14#section-6.9.2
+// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.9.2
 
 // API for child classes
 // ---------------------
 
 // * **new Flow([flowControlId])**: creating a new flow that will listen for WINDOW_UPDATES frames
 //   with the given `flowControlId` (or every update frame if not given)
 //
 // * **_send()**: called when more frames should be pushed. The child class is expected to override
@@ -76,17 +76,19 @@ Flow.prototype._receive = function _rece
   throw new Error('The _receive(frame, callback) method has to be overridden by the child class!');
 };
 
 // `_receive` is called by `_write` which in turn is [called by Duplex][1] when someone `write()`s
 // to the flow. It emits the 'receiving' event and notifies the window size tracking code if the
 // incoming frame is a WINDOW_UPDATE.
 // [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
 Flow.prototype._write = function _write(frame, encoding, callback) {
-  if (frame.flags.END_STREAM || (frame.type === 'RST_STREAM')) {
+  var sentToUs = (this._flowControlId === undefined) || (frame.stream === this._flowControlId);
+
+  if (sentToUs && (frame.flags.END_STREAM || (frame.type === 'RST_STREAM'))) {
     this._ended = true;
   }
 
   if ((frame.type === 'DATA') && (frame.data.length > 0)) {
     this._receive(frame, function() {
       this._received += frame.data.length;
       if (!this._restoreWindowTimer) {
         this._restoreWindowTimer = setImmediate(this._restoreWindow.bind(this));
@@ -94,18 +96,17 @@ Flow.prototype._write = function _write(
       callback();
     }.bind(this));
   }
 
   else {
     this._receive(frame, callback);
   }
 
-  if ((frame.type === 'WINDOW_UPDATE') &&
-      ((this._flowControlId === undefined) || (frame.stream === this._flowControlId))) {
+  if (sentToUs && (frame.type === 'WINDOW_UPDATE')) {
     this._updateWindow(frame);
   }
 };
 
 // `_restoreWindow` basically acknowledges the DATA frames received since it's last call. It sends
 // a WINDOW_UPDATE that restores the flow control window of the remote end.
 // TODO: push this directly into the output queue. No need to wait for DATA frames in the queue.
 Flow.prototype._restoreWindow = function _restoreWindow() {
--- a/testing/xpcshell/node-http2/lib/protocol/framer.js
+++ b/testing/xpcshell/node-http2/lib/protocol/framer.js
@@ -141,17 +141,17 @@ Deserializer.prototype._transform = func
       }
       this._next(COMMON_HEADER_SIZE);
     }
   }
 
   done();
 };
 
-// [Frame Header](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-4.1)
+// [Frame Header](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1)
 // --------------------------------------------------------------
 //
 // 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,384 octets by default, though this can be increased by a receiver.
 //
@@ -264,17 +264,17 @@ Deserializer.commonHeader = function rea
 
 // Every frame type is registered in the following places:
 //
 // * `frameTypes`: a register of frame type codes (used by `commonHeader()`)
 // * `frameFlags`: a register of valid flags for frame types (used by `commonHeader()`)
 // * `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-14#section-6.1)
+// [DATA Frames](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.1)
 // ------------------------------------------------------------
 //
 // DATA frames (type=0x0) convey arbitrary, variable-length sequences of octets associated with a
 // stream.
 //
 // The DATA frame defines the following flags:
 //
 // * END_STREAM (0x1):
@@ -303,17 +303,17 @@ Deserializer.DATA = function readData(bu
 
   if (paddingLength) {
     frame.data = buffer.slice(dataOffset, -1 * paddingLength);
   } else {
     frame.data = buffer.slice(dataOffset);
   }
 };
 
-// [HEADERS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.2)
+// [HEADERS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.2)
 // --------------------------------------------------------------
 //
 // The HEADERS frame (type=0x1) allows the sender to create a stream.
 //
 // The HEADERS frame defines the following flags:
 //
 // * END_STREAM (0x1):
 //   Bit 1 being set indicates that this frame is the last that the endpoint will send for the
@@ -385,17 +385,17 @@ Deserializer.HEADERS = function readHead
 
   if (paddingLength) {
     frame.data = buffer.slice(dataOffset, -1 * paddingLength);
   } else {
     frame.data = buffer.slice(dataOffset);
   }
 };
 
-// [PRIORITY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.3)
+// [PRIORITY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.3)
 // -------------------------------------------------------
 //
 // The PRIORITY frame (type=0x2) specifies the sender-advised priority of a stream.
 //
 // The PRIORITY frame does not define any flags.
 
 frameTypes[0x2] = 'PRIORITY';
 
@@ -430,17 +430,17 @@ Deserializer.PRIORITY = function readPri
   var dependencyData = new Buffer(4);
   buffer.copy(dependencyData, 0, 0, 4);
   frame.exclusiveDependency = !!(dependencyData[0] & 0x80);
   dependencyData[0] &= 0x7f;
   frame.priorityDependency = dependencyData.readUInt32BE(0);
   frame.priorityWeight = buffer.readUInt8(4);
 };
 
-// [RST_STREAM](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.4)
+// [RST_STREAM](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.4)
 // -----------------------------------------------------------
 //
 // The RST_STREAM frame (type=0x3) allows for abnormal termination of a stream.
 //
 // No type-flags are defined.
 
 frameTypes[0x3] = 'RST_STREAM';
 
@@ -468,17 +468,17 @@ Serializer.RST_STREAM = function writeRs
 Deserializer.RST_STREAM = function readRstStream(buffer, frame) {
   frame.error = errorCodes[buffer.readUInt32BE(0)];
   if (!frame.error) {
     // Unknown error codes are considered equivalent to INTERNAL_ERROR
     frame.error = 'INTERNAL_ERROR';
   }
 };
 
-// [SETTINGS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.5)
+// [SETTINGS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.5)
 // -------------------------------------------------------
 //
 // The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints
 // communicate.
 //
 // The SETTINGS frame defines the following flag:
 
 // * ACK (0x1):
@@ -575,17 +575,17 @@ definedSettings[3] = { name: 'SETTINGS_M
 // * SETTINGS_INITIAL_WINDOW_SIZE (4):
 //   indicates the sender's initial stream window size (in bytes) for new streams.
 definedSettings[4] = { name: 'SETTINGS_INITIAL_WINDOW_SIZE', flag: false };
 
 // * 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)
+// [PUSH_PROMISE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.6)
 // ---------------------------------------------------------------
 //
 // The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of streams the
 // sender intends to initiate.
 //
 // The PUSH_PROMISE frame defines the following flags:
 //
 // * END_PUSH_PROMISE (0x4):
@@ -636,17 +636,17 @@ Deserializer.PUSH_PROMISE = function rea
   dataOffset += 4;
   if (paddingLength) {
     frame.data = buffer.slice(dataOffset, -1 * paddingLength);
   } else {
     frame.data = buffer.slice(dataOffset);
   }
 };
 
-// [PING](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.7)
+// [PING](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.7)
 // -----------------------------------------------
 //
 // The PING frame (type=0x6) is a mechanism for measuring a minimal round-trip time from the
 // sender, as well as determining whether an idle connection is still functional.
 //
 // The PING frame defines one type-specific flag:
 //
 // * ACK (0x1):
@@ -666,17 +666,17 @@ Serializer.PING = function writePing(fra
 
 Deserializer.PING = function readPing(buffer, frame) {
   if (buffer.length !== 8) {
     return 'FRAME_SIZE_ERROR';
   }
   frame.data = buffer;
 };
 
-// [GOAWAY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.8)
+// [GOAWAY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.8)
 // ---------------------------------------------------
 //
 // The GOAWAY frame (type=0x7) informs the remote peer to stop creating streams on this connection.
 //
 // The GOAWAY frame does not define any flags.
 
 frameTypes[0x7] = 'GOAWAY';
 
@@ -717,17 +717,17 @@ Deserializer.GOAWAY = function readGoawa
   frame.last_stream = buffer.readUInt32BE(0) & 0x7fffffff;
   frame.error = errorCodes[buffer.readUInt32BE(4)];
   if (!frame.error) {
     // Unknown error types are to be considered equivalent to INTERNAL ERROR
     frame.error = 'INTERNAL_ERROR';
   }
 };
 
-// [WINDOW_UPDATE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.9)
+// [WINDOW_UPDATE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.9)
 // -----------------------------------------------------------------
 //
 // The WINDOW_UPDATE frame (type=0x8) is used to implement flow control.
 //
 // The WINDOW_UPDATE frame does not define any flags.
 
 frameTypes[0x8] = 'WINDOW_UPDATE';
 
@@ -755,17 +755,17 @@ Deserializer.WINDOW_UPDATE = function re
     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-14#section-6.10)
+// [CONTINUATION](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.10)
 // ------------------------------------------------------------
 //
 // The CONTINUATION frame (type=0x9) is used to continue a sequence of header block fragments.
 //
 // The CONTINUATION frame defines the following flag:
 //
 // * END_HEADERS (0x4):
 //   The END_HEADERS bit indicates that this frame ends the sequence of header block fragments
@@ -780,17 +780,17 @@ typeSpecificAttributes.CONTINUATION = ['
 Serializer.CONTINUATION = function writeContinuation(frame, buffers) {
   buffers.push(frame.data);
 };
 
 Deserializer.CONTINUATION = function readContinuation(buffer, frame) {
   frame.data = buffer;
 };
 
-// [ALTSVC](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.11)
+// [ALTSVC](http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-04#section-4)
 // ------------------------------------------------------------
 //
 // The ALTSVC frame (type=0xA) advertises the availability of an alternative service to the client.
 //
 // The ALTSVC frame does not define any flags.
 
 frameTypes[0xA] = 'ALTSVC';
 
@@ -869,17 +869,17 @@ Deserializer.ALTSVC = function readAltSv
   frame.port = buffer.readUInt16BE(4);
   var pidLength = buffer.readUInt8(7);
   frame.protocolID = buffer.toString('ascii', 8, 8 + pidLength);
   var hostLength = buffer.readUInt8(8 + pidLength);
   frame.host = buffer.toString('ascii', 9 + pidLength, 9 + pidLength + hostLength);
   frame.origin = buffer.toString('ascii', 9 + pidLength + hostLength);
 };
 
-// [BLOCKED](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.12)
+// BLOCKED
 // ------------------------------------------------------------
 //
 // The BLOCKED frame (type=0xB) indicates that the sender is unable to send data
 // due to a closed flow control window.
 //
 // The BLOCKED frame does not define any flags and contains no payload.
 
 frameTypes[0xB] = 'BLOCKED';
@@ -889,17 +889,17 @@ frameFlags.BLOCKED = [];
 typeSpecificAttributes.BLOCKED = [];
 
 Serializer.BLOCKED = function writeBlocked(frame, buffers) {
 };
 
 Deserializer.BLOCKED = function readBlocked(buffer, frame) {
 };
 
-// [Error Codes](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-7)
+// [Error Codes](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-7)
 // ------------------------------------------------------------
 
 var errorCodes = [
   'NO_ERROR',
   'PROTOCOL_ERROR',
   'INTERNAL_ERROR',
   'FLOW_CONTROL_ERROR',
   'SETTINGS_TIMEOUT',
--- a/testing/xpcshell/node-http2/lib/protocol/index.js
+++ b/testing/xpcshell/node-http2/lib/protocol/index.js
@@ -1,9 +1,9 @@
-// [node-http2-protocol][homepage] is an implementation of the [HTTP/2 (draft 14)][http2]
+// [node-http2-protocol][homepage] is an implementation of the [HTTP/2 (draft 16)][http2]
 // framing layer for [node.js][node].
 //
 // The main building blocks are [node.js streams][node-stream] that are connected through pipes.
 //
 // The main components are:
 //
 // * [Endpoint](endpoint.html): represents an HTTP/2 endpoint (client or server). It's
 //   responsible for the the first part of the handshake process (sending/receiving the
@@ -23,20 +23,20 @@
 //
 // * [Compressor and Decompressor](compressor.html): compression and decompression of HEADER and
 //   PUSH_PROMISE frames
 //
 // * [Serializer and Deserializer](framer.html): the lowest layer in the stack that transforms
 //   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-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
+// [http2]:               http://tools.ietf.org/html/draft-ietf-httpbis-http2-16
+// [http2-connheader]:    http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-3.5
+// [http2-stream]:        http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5
+// [http2-streamstate]:   http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#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.VERSION = 'h2-16';
 
 exports.Endpoint = require('./endpoint').Endpoint;
--- a/testing/xpcshell/node-http2/lib/protocol/stream.js
+++ b/testing/xpcshell/node-http2/lib/protocol/stream.js
@@ -347,17 +347,17 @@ Stream.prototype._finishing = function _
     this._log.debug({ frame: lastFrame }, 'Marking last frame with END_STREAM flag.');
     lastFrame.flags.END_STREAM = true;
     this._transition(true, endFrame);
   } else {
     this._pushUpstream(endFrame);
   }
 };
 
-// [Stream States](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.1)
+// [Stream States](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.1)
 // ----------------
 //
 //                           +--------+
 //                     PP    |        |    PP
 //                  ,--------|  idle  |--------.
 //                 /         |        |         \
 //                v          +--------+          v
 //         +----------+          |           +----------+
@@ -459,17 +459,17 @@ Stream.prototype._transition = function 
     //   releases the stream reservation.
     // * An endpoint may receive PRIORITY frame in this state.
     // * An endpoint MUST NOT send any other type of frame in this state.
     case 'RESERVED_LOCAL':
       if (sending && HEADERS) {
         this._setState('HALF_CLOSED_REMOTE');
       } else if (RST_STREAM) {
         this._setState('CLOSED');
-      } else if (receiving && PRIORITY) {
+      } else if (PRIORITY) {
         /* No state change */
       } else {
         connectionError = 'PROTOCOL_ERROR';
       }
       break;
 
     // A stream in the **reserved (remote)** state has been reserved by a remote peer.
     //
@@ -478,17 +478,17 @@ Stream.prototype._transition = function 
     // * Receiving a HEADERS frame causes the stream to transition to "half closed (local)".
     // * An endpoint MAY send PRIORITY frames in this state to reprioritize the stream.
     // * Receiving any other type of frame MUST be treated as a stream error of type PROTOCOL_ERROR.
     case 'RESERVED_REMOTE':
       if (RST_STREAM) {
         this._setState('CLOSED');
       } else if (receiving && HEADERS) {
         this._setState('HALF_CLOSED_LOCAL');
-      } else if (BLOCKED || (sending && PRIORITY)) {
+      } else if (BLOCKED || PRIORITY) {
         /* No state change */
       } else {
         connectionError = 'PROTOCOL_ERROR';
       }
       break;
 
     // The **open** state is where both peers can send frames. In this state, sending peers observe
     // advertised stream level flow control limits.
@@ -513,17 +513,17 @@ Stream.prototype._transition = function 
     //
     // * A stream transitions from this state to "closed" when a frame that contains a END_STREAM
     //   flag is received, or when either peer sends a RST_STREAM frame.
     // * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream.
     // * WINDOW_UPDATE can be sent by a peer that has sent a frame bearing the END_STREAM flag.
     case 'HALF_CLOSED_LOCAL':
       if (RST_STREAM || (receiving && frame.flags.END_STREAM)) {
         this._setState('CLOSED');
-      } else if (BLOCKED || ALTSVC ||receiving || (sending && (PRIORITY || WINDOW_UPDATE))) {
+      } else if (BLOCKED || ALTSVC || receiving || PRIORITY || (sending && WINDOW_UPDATE)) {
         /* No state change */
       } else {
         connectionError = 'PROTOCOL_ERROR';
       }
       break;
 
     // A stream that is **half closed (remote)** is no longer being used by the peer to send frames.
     // In this state, an endpoint is no longer obligated to maintain a receiver flow control window
@@ -533,17 +533,17 @@ Stream.prototype._transition = function 
     //   respond with a stream error of type STREAM_CLOSED.
     // * A stream can transition from this state to "closed" by sending a frame that contains a
     //   END_STREAM flag, or when either peer sends a RST_STREAM frame.
     // * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream.
     // * A receiver MAY receive a WINDOW_UPDATE frame on a "half closed (remote)" stream.
     case 'HALF_CLOSED_REMOTE':
       if (RST_STREAM || (sending && frame.flags.END_STREAM)) {
         this._setState('CLOSED');
-      } else if (BLOCKED || ALTSVC ||sending || (receiving && (WINDOW_UPDATE || PRIORITY))) {
+      } else if (BLOCKED || ALTSVC || sending || PRIORITY || (receiving && WINDOW_UPDATE)) {
         /* No state change */
       } else {
         connectionError = 'PROTOCOL_ERROR';
       }
       break;
 
     // The **closed** state is the terminal state.
     //
@@ -561,19 +561,19 @@ Stream.prototype._transition = function 
     //   that cannot be withdrawn. An endpoint that sends a RST_STREAM frame MUST ignore frames that
     //   it receives on closed streams after it has sent a RST_STREAM frame. An endpoint MAY choose
     //   to limit the period over which it ignores frames and treat frames that arrive after this
     //   time as being in error.
     // * An endpoint might receive a PUSH_PROMISE frame after it sends RST_STREAM. PUSH_PROMISE
     //   causes a stream to become "reserved". If promised streams are not desired, a RST_STREAM
     //   can be used to close any of those streams.
     case 'CLOSED':
-      if ((sending && RST_STREAM) ||
+      if (PRIORITY || (sending && RST_STREAM) ||
           (receiving && this._closedByUs &&
-           (this._closedWithRst || WINDOW_UPDATE || PRIORITY || RST_STREAM || ALTSVC))) {
+           (this._closedWithRst || WINDOW_UPDATE || RST_STREAM || ALTSVC))) {
         /* No state change */
       } else {
         streamError = 'STREAM_CLOSED';
       }
       break;
   }
 
   // Noting that the connection was closed by the other endpoint. It may be important in edge cases.
@@ -632,17 +632,17 @@ Stream.prototype._transition = function 
     // * When receiving something invalid, sending an RST_STREAM using the `reset` method.
     //   This will automatically cause a transition to the CLOSED state.
     else {
       this._log.error(info, 'Received illegal frame.');
       if (connectionError) {
         this.emit('connectionError', connectionError);
       } else {
         this.reset(streamError);
-        this.emit('error', streamError)
+        this.emit('error', streamError);
       }
     }
   }
 };
 
 // Bunyan serializers
 // ------------------
 
--- a/testing/xpcshell/node-http2/package.json
+++ b/testing/xpcshell/node-http2/package.json
@@ -1,25 +1,25 @@
 {
   "name": "http2",
-  "version": "3.0.0",
+  "version": "3.1.0",
   "description": "An HTTP/2 client and server implementation",
   "main": "lib/index.js",
   "engines" : {
     "node" : ">=0.10.19"
   },
   "devDependencies": {
     "istanbul": "*",
     "chai": "*",
     "mocha": "*",
     "docco": "*",
     "bunyan": "*"
   },
   "scripts": {
-    "test": "istanbul test _mocha -- --reporter spec --slow 200",
+    "test": "istanbul test _mocha -- --reporter spec --slow 500 --timeout 15000",
     "doc": "docco lib/* --output doc --layout parallel --css doc/docco.css"
   },
   "repository": {
     "type": "git",
     "url": "https://github.com/molnarg/node-http2.git"
   },
   "homepage": "https://github.com/molnarg/node-http2",
   "bugs": {
@@ -31,13 +31,16 @@
     "client",
     "server"
   ],
   "author": "Gábor Molnár <gabor@molnar.es> (http://gabor.molnar.es)",
   "contributors": [
     "Nick Hurley",
     "Mike Belshe",
     "Yoshihiro Iwanaga",
-    "vsemogutor"
+    "Igor Novikov",
+    "James Willcox",
+    "David Björklund",
+    "Patrick McManus"
   ],
   "license": "MIT",
   "readmeFilename": "README.md"
 }
--- a/testing/xpcshell/node-http2/test/compressor.js
+++ b/testing/xpcshell/node-http2/test/compressor.js
@@ -401,31 +401,31 @@ describe('compressor.js', function() {
           expect(table.encode(new Buffer(decoded)).toString('hex')).to.equal(encoded);
         }
         table = HuffmanTable.huffmanTable;
         for (decoded in test_huffman_response) {
           encoded = test_huffman_response[decoded];
           expect(table.encode(new Buffer(decoded)).toString('hex')).to.equal(encoded);
         }
       });
-    })
+    });
     describe('method decode(buffer)', function() {
       it('should return the Huffman decoded version of the input buffer', function() {
         var table = HuffmanTable.huffmanTable;
         for (var decoded in test_huffman_request) {
           var encoded = test_huffman_request[decoded];
-          expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded)
+          expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded);
         }
         table = HuffmanTable.huffmanTable;
         for (decoded in test_huffman_response) {
           encoded = test_huffman_response[decoded];
-          expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded)
+          expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded);
         }
       });
-    })
+    });
   });
 
   describe('HeaderSetCompressor', function() {
     describe('static method .integer(I, N)', function() {
       it('should return an array of buffers that represent the N-prefix coded form of the integer I', function() {
         for (var i = 0; i < test_integers.length; i++) {
           var test = test_integers[i];
           test.buffer.cursor = 0;
@@ -564,12 +564,12 @@ describe('compressor.js', function() {
           while (Math.random() > 0.1) {
             buffer.push(Math.floor(Math.random() * 256))
           }
           buffer = new Buffer(buffer);
           var table = HuffmanTable.huffmanTable;
           var result = table.decode(table.encode(buffer));
           expect(result).to.deep.equal(buffer);
         }
-      })
-    })
+      });
+    });
   });
 });
--- a/testing/xpcshell/node-http2/test/connection.js
+++ b/testing/xpcshell/node-http2/test/connection.js
@@ -126,17 +126,17 @@ describe('connection.js', function() {
         done = util.callNTimes(2, done);
         client_stream.on('headers', function(headers) {
           expect(headers).to.deep.equal(response_headers);
           done();
         });
         client_stream.on('data', function(data) {
           expect(data).to.deep.equal(response_data);
           done();
-        })
+        });
       });
     });
     describe('server push', function() {
       it('should work as expected', function(done) {
         var request_headers = { ':method': 'get', ':path': '/' };
         var response_headers = { ':status': '200' };
         var push_request_headers = { ':method': 'get', ':path': '/x' };
         var push_response_headers = { ':status': '200' };
--- a/testing/xpcshell/node-http2/test/flow.js
+++ b/testing/xpcshell/node-http2/test/flow.js
@@ -249,12 +249,12 @@ describe('flow.js', function() {
 
           expect(input).to.deep.equal(output);
 
           done();
         });
 
         // Start piping
         flow1.pipe(flow2).pipe(flow1);
-      })
+      });
     });
   });
 });
--- a/testing/xpcshell/node-http2/test/http.js
+++ b/testing/xpcshell/node-http2/test/http.js
@@ -119,17 +119,17 @@ describe('http.js', function() {
           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)
+      response.writeHead(404);
     });
   });
   describe('test scenario', function() {
     describe('simple request', function() {
       it('should work as expected', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
@@ -144,16 +144,76 @@ describe('http.js', function() {
               expect(data.toString()).to.equal(message);
               server.close();
               done();
             });
           });
         });
       });
     });
+    describe('2 simple request in parallel', function() {
+      it('should work as expected', function(originalDone) {
+        var path = '/x';
+        var message = 'Hello world';
+        done = util.callNTimes(2, function() {
+          server.close();
+          originalDone();
+        });
+
+        var server = http2.createServer(options, function(request, response) {
+          expect(request.url).to.equal(path);
+          response.end(message);
+        });
+
+        server.listen(1234, function() {
+          http2.get('https://localhost:1234' + path, function(response) {
+            response.on('data', function(data) {
+              expect(data.toString()).to.equal(message);
+              done();
+            });
+          });
+          http2.get('https://localhost:1234' + path, function(response) {
+            response.on('data', function(data) {
+              expect(data.toString()).to.equal(message);
+              done();
+            });
+          });
+        });
+      });
+    });
+    describe('100 simple request in a series', function() {
+      it('should work as expected', function(done) {
+        var path = '/x';
+        var message = 'Hello world';
+
+        var server = http2.createServer(options, function(request, response) {
+          expect(request.url).to.equal(path);
+          response.end(message);
+        });
+
+        var n = 100;
+        server.listen(1242, function() {
+          doRequest();
+          function doRequest() {
+            http2.get('https://localhost:1242' + path, function(response) {
+              response.on('data', function(data) {
+                expect(data.toString()).to.equal(message);
+                if (n) {
+                  n -= 1;
+                  doRequest();
+                } else {
+                  server.close();
+                  done();
+                }
+              });
+            });
+          }
+        });
+      });
+    });
     describe('request with payload', function() {
       it('should work as expected', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
         var server = http2.createServer(options, function(request, response) {
           expect(request.url).to.equal(path);
           request.once('data', function(data) {
@@ -235,17 +295,16 @@ describe('http.js', function() {
       });
     });
     describe('request over plain TCP', function() {
       it('should work as expected', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
         var server = http2.raw.createServer({
-          plain: true,
           log: util.serverLog
         }, function(request, response) {
           expect(request.url).to.equal(path);
           response.end(message);
         });
 
         server.listen(1237, function() {
           var request = http2.raw.request({
@@ -259,16 +318,40 @@ describe('http.js', function() {
               server.close();
               done();
             });
           });
           request.end();
         });
       });
     });
+    describe('get over plain TCP', function() {
+      it('should work as expected', function(done) {
+        var path = '/x';
+        var message = 'Hello world';
+
+        var server = http2.raw.createServer({
+          log: util.serverLog
+        }, function(request, response) {
+          expect(request.url).to.equal(path);
+          response.end(message);
+        });
+
+        server.listen(1237, function() {
+          var request = http2.raw.get('http://localhost:1237/x', function(response) {
+            response.on('data', function(data) {
+              expect(data.toString()).to.equal(message);
+              server.close();
+              done();
+            });
+          });
+          request.end();
+        });
+      });
+    });
     describe('request to an HTTPS/1 server', function() {
       it('should fall back to HTTPS/1 successfully', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
         var server = https.createServer(options, function(request, response) {
           expect(request.url).to.equal(path);
           response.end(message);
@@ -279,16 +362,46 @@ describe('http.js', function() {
             response.on('data', function(data) {
               expect(data.toString()).to.equal(message);
               done();
             });
           });
         });
       });
     });
+    describe('2 parallel request to an HTTPS/1 server', function() {
+      it('should fall back to HTTPS/1 successfully', function(originalDone) {
+        var path = '/x';
+        var message = 'Hello world';
+        done = util.callNTimes(2, function() {
+          server.close();
+          originalDone();
+        });
+
+        var server = https.createServer(options, function(request, response) {
+          expect(request.url).to.equal(path);
+          response.end(message);
+        });
+
+        server.listen(6789, function() {
+          http2.get('https://localhost:6789' + path, function(response) {
+            response.on('data', function(data) {
+              expect(data.toString()).to.equal(message);
+              done();
+            });
+          });
+          http2.get('https://localhost:6789' + path, function(response) {
+            response.on('data', function(data) {
+              expect(data.toString()).to.equal(message);
+              done();
+            });
+          });
+        });
+      });
+    });
     describe('HTTPS/1 request to a HTTP/2 server', function() {
       it('should fall back to HTTPS/1 successfully', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
         var server = http2.createServer(options, function(request, response) {
           expect(request.url).to.equal(path);
           response.end(message);
--- a/testing/xpcshell/node-http2/test/stream.js
+++ b/testing/xpcshell/node-http2/test/stream.js
@@ -107,30 +107,28 @@ var example_frames = [
   { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
   { type: 'DATA', flags: {}, data: new Buffer(5) },
   { type: 'PUSH_PROMISE', flags: {}, headers: {}, promised_stream: new Stream(util.log, null) }
 ];
 
 var invalid_incoming_frames = {
   IDLE: [
     { type: 'DATA', flags: {}, data: new Buffer(5) },
-    { type: 'PRIORITY', flags: {}, priority: 1 },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} },
     { type: 'RST_STREAM', flags: {}, error: 'CANCEL' }
   ],
   RESERVED_LOCAL: [
     { type: 'DATA', flags: {}, data: new Buffer(5) },
     { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
   ],
   RESERVED_REMOTE: [
     { type: 'DATA', flags: {}, data: new Buffer(5) },
-    { type: 'PRIORITY', flags: {}, priority: 1 },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
   ],
   OPEN: [
   ],
   HALF_CLOSED_LOCAL: [
   ],
   HALF_CLOSED_REMOTE: [
@@ -138,23 +136,21 @@ var invalid_incoming_frames = {
     { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} }
   ]
 };
 
 var invalid_outgoing_frames = {
   IDLE: [
     { type: 'DATA', flags: {}, data: new Buffer(5) },
-    { type: 'PRIORITY', flags: {}, priority: 1 },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} }
   ],
   RESERVED_LOCAL: [
     { type: 'DATA', flags: {}, data: new Buffer(5) },
-    { type: 'PRIORITY', flags: {}, priority: 1 },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
   ],
   RESERVED_REMOTE: [
     { type: 'DATA', flags: {}, data: new Buffer(5) },
     { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
@@ -164,17 +160,16 @@ var invalid_outgoing_frames = {
   HALF_CLOSED_LOCAL: [
     { type: 'DATA', flags: {}, data: new Buffer(5) },
     { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} }
   ],
   HALF_CLOSED_REMOTE: [
   ],
   CLOSED: [
-    { type: 'PRIORITY', flags: {}, priority: 1 },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} },
     { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
     { type: 'DATA', flags: {}, data: new Buffer(5) },
     { type: 'PUSH_PROMISE', flags: {}, headers: {}, promised_stream: new Stream(util.log, null) }
   ]
 };
 
 describe('stream.js', function() {
@@ -183,26 +178,26 @@ describe('stream.js', function() {
       it('should emit error, and answer RST_STREAM for invalid incoming frames', function() {
         Object.keys(invalid_incoming_frames).forEach(function(state) {
           invalid_incoming_frames[state].forEach(function(invalid_frame) {
             var stream = createStream();
             var connectionErrorHappened = false;
             stream.state = state;
             stream.once('connectionError', function() { connectionErrorHappened = true; });
             stream._transition(false, invalid_frame);
-            expect(connectionErrorHappened)
+            expect(connectionErrorHappened);
           });
         });
 
         // CLOSED state as a result of incoming END_STREAM (or RST_STREAM)
         var stream = createStream();
         stream.headers({});
         stream.end();
         stream.upstream.write({ type: 'HEADERS', headers:{}, flags: { END_STREAM: true }, count_change: util.noop });
-        example_frames.forEach(function(invalid_frame) {
+        example_frames.slice(1).forEach(function(invalid_frame) {
           invalid_frame.count_change = util.noop;
           expect(stream._transition.bind(stream, false, invalid_frame)).to.throw('Uncaught, unspecified "error" event.');
         });
 
         // CLOSED state as a result of outgoing END_STREAM
         stream = createStream();
         stream.upstream.write({ type: 'HEADERS', headers:{}, flags: { END_STREAM: true }, count_change: util.noop });
         stream.headers({});
--- a/testing/xpcshell/node-http2/test/util.js
+++ b/testing/xpcshell/node-http2/test/util.js
@@ -81,9 +81,9 @@ exports.shuffleBuffers = function shuffl
 
   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;
-}
+};