bug 1124717 - 2/4 update CI to node-http2 3.1.0 to fix flow control issue r=hurley draft
authorPatrick McManus <mcmanus@ducksong.com>
Mon, 26 Jan 2015 20:21:54 -0500
changeset 239226 48a86b800b56c2e22b5260a6d94edd475b694f30
parent 239225 4761861c990084c7e986ac0df7828884cbae6b53
child 239227 0dfd56e1bc81d5b34ad7f2bb3b48c34f804cd544
push id489
push usermcmanus@ducksong.com
push dateTue, 27 Jan 2015 01:44:53 +0000
reviewershurley
bugs1124717
milestone38.0a1
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;
-}
+};