From a46455fe85435655880e836ce5e5f460c131c036 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Nov 2015 10:59:59 +0100 Subject: [PATCH 01/41] Forward `listening` and `error` events. --- lib/http.js | 33 +++++++++++++++++++++++++++++++++ test/http.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/lib/http.js b/lib/http.js index 4efc5fe..648537b 100644 --- a/lib/http.js +++ b/lib/http.js @@ -396,6 +396,36 @@ exports.IncomingRequest = IncomingRequest; exports.OutgoingResponse = OutgoingResponse; exports.ServerResponse = OutgoingResponse; // for API compatibility +// Forward events `event` on `source` to all listeners on `target`. +// +// Note: The calling context is `source`. +function forwardEvent(event, source, target) { + function forward() { + var listeners = target.listeners(event); + + var n = listeners.length; + + // Special case for `error` event with no listeners. + if (n === 0 && event === 'error') { + var args = [event]; + args.push.apply(args, arguments); + + target.emit.apply(target, args); + return; + } + + for (var i = 0; i < n; ++i) { + listeners[i].apply(source, arguments); + } + } + + source.on(event, forward); + + // A reference to the function is necessary to be able to stop + // forwarding. + return forward; +} + // Server class // ------------ @@ -430,6 +460,9 @@ function Server(options) { } }); this._server.on('request', this.emit.bind(this, 'request')); + + forwardEvent('error', this._server, this); + forwardEvent('listening', this._server, this); } // HTTP2 over plain TCP diff --git a/test/http.js b/test/http.js index d5c64c6..96d43f1 100644 --- a/test/http.js +++ b/test/http.js @@ -38,6 +38,36 @@ describe('http.js', function() { }).to.throw(Error); }); }); + describe('method `listen()`', function () { + it('should emit `listening` event', function (done) { + var server = http2.createServer(serverOptions); + + server.on('listening', function () { + server.close(); + + done(); + }) + + server.listen(0); + }); + it('should emit `error` on failure', function (done) { + var server = http2.createServer(serverOptions); + + // This TCP server is used to explicitly take a port to make + // server.listen() fails. + var net = require('net').createServer(); + + server.on('error', function () { + net.close() + + done(); + }); + + net.listen(0, function () { + server.listen(this.address().port); + }); + }); + }); describe('property `timeout`', function() { it('should be a proxy for the backing HTTPS server\'s `timeout` property', function() { var server = new http2.Server(serverOptions); From 75d882c7a424c2ed68c2e600fd2592769a95575c Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Fri, 8 Jan 2016 12:28:39 -0800 Subject: [PATCH 02/41] Expose request.socket as request.connection This makes us compatibile with (among others?) hapi Closes #144 Originally by Aleksey Shvayka --- lib/http.js | 2 +- test/http.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/http.js b/lib/http.js index 4efc5fe..3894272 100644 --- a/lib/http.js +++ b/lib/http.js @@ -468,7 +468,7 @@ Server.prototype._start = function _start(socket) { // Some conformance to Node.js Https specs allows to distinguish clients: request.remoteAddress = socket.remoteAddress; request.remotePort = socket.remotePort; - request.socket = socket; + request.connection = request.socket = socket; request.once('ready', self.emit.bind(self, 'request', request, response)); }); diff --git a/test/http.js b/test/http.js index d5c64c6..29d57d3 100644 --- a/test/http.js +++ b/test/http.js @@ -3,6 +3,7 @@ var util = require('./util'); var fs = require('fs'); var path = require('path'); var url = require('url'); +var net = require('net'); var http2 = require('../lib/http'); var https = require('https'); @@ -588,6 +589,26 @@ describe('http.js', function() { }); }); }); + it('should expose net.Socket as .socket and .connection', function(done) { + var server = http2.createServer(serverOptions, function(request, response) { + expect(request.socket).to.equal(request.connection); + expect(request.socket).to.be.instanceof(net.Socket); + response.write('Pong'); + response.end(); + done(); + }); + + server.listen(1248, 'localhost', function() { + var request = https.request({ + host: 'localhost', + port: 1248, + path: '/', + ca: serverOptions.cert + }); + request.write('Ping'); + request.end(); + }); + }); }); describe('request and response with trailers', function() { it('should work as expected', function(done) { From 46575d86a893676252666490971175f401fb629d Mon Sep 17 00:00:00 2001 From: Tim McKenzie Date: Fri, 14 Aug 2015 09:59:26 -0700 Subject: [PATCH 03/41] Re-emit socket errors for client requests. Old behaviour was to throw unhandled exeptions. This fix should handle exceptions from both plain text and encrypted requests. Includes tests for plain and encryped. --- lib/http.js | 14 ++++++++++++-- test/http.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/lib/http.js b/lib/http.js index 5e10386..02f51c9 100644 --- a/lib/http.js +++ b/lib/http.js @@ -849,6 +849,7 @@ Agent.prototype.request = function request(options, callback) { options.host, options.port ].join(':'); + var self = this; // * There's an existing HTTP/2 connection to this host if (key in this.endpoints) { @@ -864,7 +865,12 @@ Agent.prototype.request = function request(options, callback) { port: options.port, localAddress: options.localAddress }); - + + endpoint.socket.on('error', function (error) { + self._log.error('Socket error: ' + error.toString()); + request.emit('error', error); + }); + this.endpoints[key] = endpoint; endpoint.pipe(endpoint.socket).pipe(endpoint); request._start(endpoint.createStream(), options); @@ -885,6 +891,11 @@ Agent.prototype.request = function request(options, callback) { } var httpsRequest = https.request(options); + httpsRequest.on('error', function (error) { + self._log.error('Socket error: ' + error.toString()); + request.emit('error', error); + }); + httpsRequest.on('socket', function(socket) { var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol; if (negotiatedProtocol != null) { // null in >=0.11.0, undefined in <0.11.0 @@ -894,7 +905,6 @@ Agent.prototype.request = function request(options, callback) { } }); - var self = this; function negotiated() { var endpoint; var negotiatedProtocol = httpsRequest.socket.alpnProtocol || httpsRequest.socket.npnProtocol; diff --git a/test/http.js b/test/http.js index e3cff1d..1c94944 100644 --- a/test/http.js +++ b/test/http.js @@ -543,6 +543,52 @@ describe('http.js', function() { }); }); }); + describe('Handle socket error', function () { + it('HTTPS on Connection Refused error', function (done) { + var path = '/x'; + var request = http2.request('https://127.0.0.1:6666' + path); + + request.on('error', function (err) { + expect(err.errno).to.equal('ECONNREFUSED'); + done(); + }); + + request.on('response', function (response) { + server._server._handle.destroy(); + + response.on('data', util.noop); + + response.once('end', function () { + done(new Error('Request should have failed')); + }); + }); + + request.end(); + + }); + it('HTTP on Connection Refused error', function (done) { + var path = '/x'; + + var request = http2.raw.request('http://127.0.0.1:6666' + path); + + request.on('error', function (err) { + expect(err.errno).to.equal('ECONNREFUSED'); + done(); + }); + + request.on('response', function (response) { + server._server._handle.destroy(); + + response.on('data', util.noop); + + response.once('end', function () { + done(new Error('Request should have failed')); + }); + }); + + request.end(); + }); + }); describe('server push', function() { it('should work as expected', function(done) { var path = '/x'; From c6232c5f1362df5830536d0209295f8ce73b3893 Mon Sep 17 00:00:00 2001 From: Alan Chandler Date: Sun, 3 Jan 2016 08:17:19 +0000 Subject: [PATCH 04/41] Add 'finished' field to OutgoingResponse Closes #161 --- lib/http.js | 3 +++ test/http.js | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/http.js b/lib/http.js index 02a7fac..8b7826b 100644 --- a/lib/http.js +++ b/lib/http.js @@ -328,6 +328,7 @@ function OutgoingMessage() { this._headers = {}; this._trailers = undefined; this.headersSent = false; + this.finished = false; this.on('finish', this._finish); } @@ -350,6 +351,7 @@ OutgoingMessage.prototype._finish = function _finish() { this.stream.headers(this._trailers); } } + this.finished = true; this.stream.end(); } else { this.once('socket', this._finish.bind(this)); @@ -735,6 +737,7 @@ OutgoingResponse.prototype.write = function write() { }; OutgoingResponse.prototype.end = function end() { + this.finshed = true; this._implicitHeaders(); return OutgoingMessage.prototype.end.apply(this, arguments); }; diff --git a/test/http.js b/test/http.js index 41ec014..efd0f39 100644 --- a/test/http.js +++ b/test/http.js @@ -192,6 +192,31 @@ describe('http.js', function() { response.writeHead(200); response.writeHead(404); }); + it('field finished should be Boolean', function(){ + var stream = { _log: util.log, headers: function () {}, once: util.noop }; + var response = new http2.OutgoingResponse(stream); + expect(response.finished).to.be.a('Boolean'); + }); + it('field finished should initially be false and then go to true when response completes',function(done){ + var res; + var server = http2.createServer(serverOptions, function(request, response) { + res = response; + expect(res.finished).to.be.false; + response.end('HiThere'); + }); + server.listen(1236, function() { + http2.get('https://localhost:1236/finished-test', function(response) { + response.on('data', function(data){ + var sink = data; // + }); + response.on('end',function(){ + expect(res.finished).to.be.true; + server.close(); + done(); + }); + }); + }); + }); }); describe('test scenario', function() { describe('simple request', function() { From 6a55a68377b04afb4fce01d8073c69ab23a49e4c Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Fri, 8 Jan 2016 14:34:14 -0800 Subject: [PATCH 05/41] Update note about ALPN support --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index d021049..fa57ae9 100644 --- a/README.md +++ b/README.md @@ -72,10 +72,7 @@ For a server push example, see the source code of the example Status ------ -* ALPN is not yet supported in node.js (see - [this issue](https://github.com/joyent/node/issues/5945)). For ALPN support, you will have to use - [Shigeki Ohtsu's node.js fork](https://github.com/shigeki/node/tree/alpn_support) until this code - gets merged upstream. +* ALPN is only supported in node.js >= 5.0 * Upgrade mechanism to start HTTP/2 over unencrypted channel is not implemented yet (issue [#4](https://github.com/molnarg/node-http2/issues/4)) * Other minor features found in From 4bd15effcf21bde63fa7241663e648e844c80d6d Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Fri, 8 Jan 2016 14:38:20 -0800 Subject: [PATCH 06/41] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 68f715b..7e038dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "http2", - "version": "3.2.0", + "version": "3.3.0", "description": "An HTTP/2 client and server implementation", "main": "lib/index.js", "engines" : { From aa311d227e696c34a26c8b55cbe2f6b0af45a8ad Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Mon, 11 Jan 2016 12:36:46 -0800 Subject: [PATCH 07/41] Do not continue processing streams with malformed headers When receiving a malformed HEADERS (missing one of the : headers), we would send a RST_STREAM and then continue processing the stream as if nothing had gone wrong. When the higher layer then asked us to send a response on said stream, we would try to send our own HEADERS even though the stream is CLOSED. We (righly) check to ensure that we weren't sending HEADERS on a CLOSED stream, and would crash when we did. This prevents us from getting to that point by early-returning if we don't get all the : headers we need. Fixes 145 Fixes 147 --- lib/http.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/http.js b/lib/http.js index 8b7826b..64e377c 100644 --- a/lib/http.js +++ b/lib/http.js @@ -668,6 +668,10 @@ IncomingRequest.prototype._onHeaders = function _onHeaders(headers) { this.scheme = this._checkSpecialHeader(':scheme' , headers[':scheme']); this.host = this._checkSpecialHeader(':authority', headers[':authority'] ); this.url = this._checkSpecialHeader(':path' , headers[':path'] ); + if (!this.method || !this.scheme || !this.host || !this.url) { + // This is invalid, and we've sent a RST_STREAM, so don't continue processing + return; + } // * Host header is included in the headers object for backwards compatibility. this.headers.host = this.host; From 148eccf3fe215d491ab165992d0026656076fafd Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Mon, 11 Jan 2016 14:00:43 -0800 Subject: [PATCH 08/41] Fix #146 We were not properly sanity-checking frames when trying to pass them onto a stream - so we would allow a GOAWAY to get to the handler for a stream, for instance, which would cause crashiness. This adds some sanity checks to ensure we receive frames on/off a stream as appropriate, as well as to ensure we don't create a stream unless it's from a HEADERS (which would also be illegal). --- lib/protocol/connection.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/protocol/connection.js b/lib/protocol/connection.js index d917096..84f4208 100644 --- a/lib/protocol/connection.js +++ b/lib/protocol/connection.js @@ -353,11 +353,37 @@ Connection.prototype._receive = function _receive(frame, done) { this._onFirstFrameReceived(frame); } + // Do some sanity checking here before we create a stream + if ((frame.type == 'SETTINGS' || + frame.type == 'PING' || + frame.type == 'GOAWAY') && + frame.stream != 0) { + // Got connection-level frame on a stream - EEP! + this.close('PROTOCOL_ERROR'); + return; + } else if ((frame.type == 'DATA' || + frame.type == 'HEADERS' || + frame.type == 'PRIORITY' || + frame.type == 'RST_STREAM' || + frame.type == 'PUSH_PROMISE' || + frame.type == 'CONTINUATION') && + frame.stream == 0) { + // Got stream-level frame on connection - EEP! + this.close('PROTOCOL_ERROR'); + return; + } + // WINDOW_UPDATE can be on either stream or connection + // * gets the appropriate stream from the stream registry var stream = this._streamIds[frame.stream]; // * or creates one if it's not in `this.streams` if (!stream) { + if (frame.type != 'HEADERS') { + // HEADERS is the only thing that can create a stream, freak out. + this.close('PROTOCOL_ERROR'); + return; + } stream = this._createIncomingStream(frame.stream); } From e32f7363afa69133b49152b6e963e52da0a42f78 Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Mon, 11 Jan 2016 14:24:23 -0800 Subject: [PATCH 09/41] Ensure appropriate frame sizes. We were not sanity checking the amount of data we received against the amount of data we needed to parse a frame, which would cause us to crash if we received a malformed frame. This adds a bunch of sanity checks in the Deserializer Fixes #148 --- lib/protocol/framer.js | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/lib/protocol/framer.js b/lib/protocol/framer.js index 1b32236..ca29221 100644 --- a/lib/protocol/framer.js +++ b/lib/protocol/framer.js @@ -235,6 +235,10 @@ Serializer.commonHeader = function writeCommonHeader(frame, buffers) { }; Deserializer.commonHeader = function readCommonHeader(buffer, frame) { + if (buffer.length < 9) { + return 'FRAME_SIZE_ERROR'; + } + var totallyWastedByte = buffer.readUInt8(0); var length = buffer.readUInt16BE(1); // We do this just for sanity checking later on, to make sure no one sent us a @@ -297,11 +301,19 @@ Deserializer.DATA = function readData(buffer, frame) { var dataOffset = 0; var paddingLength = 0; if (frame.flags.PADDED) { + if (buffer.length < 1) { + // We must have at least one byte for padding control, but we don't. Bad peer! + return 'FRAME_SIZE_ERROR'; + } paddingLength = (buffer.readUInt8(dataOffset) & 0xff); dataOffset = 1; } if (paddingLength) { + if (paddingLength >= (buffer.length - 1)) { + // We don't have enough room for the padding advertised - bad peer! + return 'FRAME_SIZE_ERROR'; + } frame.data = buffer.slice(dataOffset, -1 * paddingLength); } else { frame.data = buffer.slice(dataOffset); @@ -365,6 +377,18 @@ Serializer.HEADERS = function writeHeadersPriority(frame, buffers) { }; Deserializer.HEADERS = function readHeadersPriority(buffer, frame) { + var minFrameLength = 0; + if (frame.flags.PADDED) { + minFrameLength += 1; + } + if (frame.flags.PRIORITY) { + minFrameLength += 5; + } + if (buffer.length < minFrameLength) { + // Peer didn't send enough data - bad peer! + return 'FRAME_SIZE_ERROR'; + } + var dataOffset = 0; var paddingLength = 0; if (frame.flags.PADDED) { @@ -384,6 +408,10 @@ Deserializer.HEADERS = function readHeadersPriority(buffer, frame) { } if (paddingLength) { + if ((buffer.length - dataOffset) < paddingLength) { + // Not enough data left to satisfy the advertised padding - bad peer! + return 'FRAME_SIZE_ERROR'; + } frame.data = buffer.slice(dataOffset, -1 * paddingLength); } else { frame.data = buffer.slice(dataOffset); @@ -427,6 +455,10 @@ Serializer.PRIORITY = function writePriority(frame, buffers) { }; Deserializer.PRIORITY = function readPriority(buffer, frame) { + if (buffer.length < 5) { + // PRIORITY frames are 5 bytes long. Bad peer! + return 'FRAME_SIZE_ERROR'; + } var dependencyData = new Buffer(4); buffer.copy(dependencyData, 0, 0, 4); frame.exclusiveDependency = !!(dependencyData[0] & 0x80); @@ -466,6 +498,10 @@ Serializer.RST_STREAM = function writeRstStream(frame, buffers) { }; Deserializer.RST_STREAM = function readRstStream(buffer, frame) { + if (buffer.length < 4) { + // RST_STREAM is 4 bytes long. Bad peer! + return 'FRAME_SIZE_ERROR'; + } frame.error = errorCodes[buffer.readUInt32BE(0)]; if (!frame.error) { // Unknown error codes are considered equivalent to INTERNAL_ERROR @@ -626,15 +662,24 @@ Serializer.PUSH_PROMISE = function writePushPromise(frame, buffers) { }; Deserializer.PUSH_PROMISE = function readPushPromise(buffer, frame) { + if (buffer.length < 4) { + return 'FRAME_SIZE_ERROR'; + } var dataOffset = 0; var paddingLength = 0; if (frame.flags.PADDED) { + if (buffer.length < 5) { + return 'FRAME_SIZE_ERROR'; + } paddingLength = (buffer.readUInt8(dataOffset) & 0xff); dataOffset = 1; } frame.promised_stream = buffer.readUInt32BE(dataOffset) & 0x7fffffff; dataOffset += 4; if (paddingLength) { + if ((buffer.length - dataOffset) < paddingLength) { + return 'FRAME_SIZE_ERROR'; + } frame.data = buffer.slice(dataOffset, -1 * paddingLength); } else { frame.data = buffer.slice(dataOffset); @@ -714,6 +759,10 @@ Serializer.GOAWAY = function writeGoaway(frame, buffers) { }; Deserializer.GOAWAY = function readGoaway(buffer, frame) { + if (buffer.length !== 8) { + // GOAWAY must have 8 bytes + return 'FRAME_SIZE_ERROR'; + } frame.last_stream = buffer.readUInt32BE(0) & 0x7fffffff; frame.error = errorCodes[buffer.readUInt32BE(4)]; if (!frame.error) { @@ -986,7 +1035,13 @@ function unescape(s) { } Deserializer.ALTSVC = function readAltSvc(buffer, frame) { + if (buffer.length < 2) { + return 'FRAME_SIZE_ERROR'; + } var originLength = buffer.readUInt16BE(0); + if ((buffer.length - 2) < originLength) { + return 'FRAME_SIZE_ERROR'; + } frame.origin = buffer.toString('ascii', 2, 2 + originLength); var fieldValue = buffer.toString('ascii', 2 + originLength); var values = parseHeaderValue(fieldValue, ',', splitHeaderParameters); From 92c1d3bc44efff6b11de5cd85d6c4c80832a3cfe Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Mon, 11 Jan 2016 14:28:08 -0800 Subject: [PATCH 10/41] Version bump --- HISTORY.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 081826a..3cb2655 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,14 @@ Version history =============== +### 3.3.1 (2016-01-11) ### + +* Fix some DoS bugs (issues 145, 146, 147, and 148) + +### 3.3.0 (2016-01-10) ### + +* Bugfix updates from pull requests + ### 3.2.0 (2015-02-19) ### * Update ALPN token to final RFC version (h2). diff --git a/package.json b/package.json index 7e038dc..5e5ba6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "http2", - "version": "3.3.0", + "version": "3.3.1", "description": "An HTTP/2 client and server implementation", "main": "lib/index.js", "engines" : { From 1a220932f0557a07fe930078b7cf5da793949989 Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Mon, 11 Jan 2016 14:51:02 -0800 Subject: [PATCH 11/41] Roll back part of the "fix" for 146. I was incorrect to prevent anything but HEADERS from creating a stream within node-http2 - Firefox sends unused stream IDs to create a priority stream. The HEADERS-only sanity check broke that (and is wrong - we have multiple independent implementations that accept Firefox's behavior). Note that this does not actually make 146 a problem again - this was just an extra change that I slid in with the real fix for that issue. Fixes #167 --- lib/protocol/connection.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/protocol/connection.js b/lib/protocol/connection.js index 84f4208..2b86b7f 100644 --- a/lib/protocol/connection.js +++ b/lib/protocol/connection.js @@ -379,11 +379,6 @@ Connection.prototype._receive = function _receive(frame, done) { // * or creates one if it's not in `this.streams` if (!stream) { - if (frame.type != 'HEADERS') { - // HEADERS is the only thing that can create a stream, freak out. - this.close('PROTOCOL_ERROR'); - return; - } stream = this._createIncomingStream(frame.stream); } From f23df509c30657c64d4a77a0c74f6e1345d5dc63 Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Mon, 11 Jan 2016 14:53:49 -0800 Subject: [PATCH 12/41] Version bump --- HISTORY.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 3cb2655..1175321 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,10 @@ Version history =============== +### 3.3.2 (2016-01-11) ### + +* Fix an incompatibility with Firefox (issue 167) + ### 3.3.1 (2016-01-11) ### * Fix some DoS bugs (issues 145, 146, 147, and 148) diff --git a/package.json b/package.json index 5e5ba6e..8fe739f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "http2", - "version": "3.3.1", + "version": "3.3.2", "description": "An HTTP/2 client and server implementation", "main": "lib/index.js", "engines" : { From 8fefc5c7a485871b841891f824a6beb48c01a233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20So=CC=88hnlein?= Date: Thu, 14 Jan 2016 22:30:50 +0100 Subject: [PATCH 13/41] Server.listen returns instance of server to allow chaining. (and to be conform with http and https API, see https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback) --- lib/http.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/http.js b/lib/http.js index 64e377c..386ff22 100644 --- a/lib/http.js +++ b/lib/http.js @@ -536,6 +536,8 @@ Server.prototype.listen = function listen(port, hostname) { this._log.info({ on: ((typeof hostname === 'string') ? (hostname + ':' + port) : port) }, 'Listening for incoming connections'); this._server.listen.apply(this._server, arguments); + + return this._server; }; Server.prototype.close = function close(callback) { From f1fc002c1aef9b4e871c808fc5ddacdeb1a5cd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20So=CC=88hnlein?= Date: Thu, 14 Jan 2016 22:35:24 +0100 Subject: [PATCH 14/41] Server.on returns instance of server to allow chaining. (and to be conform with http and https API, see https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback) --- lib/http.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/http.js b/lib/http.js index 386ff22..b76f8e7 100644 --- a/lib/http.js +++ b/lib/http.js @@ -572,9 +572,9 @@ Object.defineProperty(Server.prototype, 'timeout', { // `server` to `this` since that means a listener. Instead, we forward the subscriptions. Server.prototype.on = function on(event, listener) { if ((event === 'upgrade') || (event === 'timeout')) { - this._server.on(event, listener && listener.bind(this)); + return this._server.on(event, listener && listener.bind(this)); } else { - EventEmitter.prototype.on.call(this, event, listener); + return EventEmitter.prototype.on.call(this, event, listener); } }; From 526279256ee4f43a670b6127217139ceea798095 Mon Sep 17 00:00:00 2001 From: Tim Emiola Date: Sun, 22 Nov 2015 13:57:34 -0800 Subject: [PATCH 15/41] Update the check on deprecated headers Allow 'te' to have value 'trailers'. From the HTTP2.0 spec: [https://httpwg.github.io/specs/rfc7540.html#rfc.section.8.1.2] HTTP/2 does not use the Connection header field to indicate connection-specific header fields; in this protocol, connection-specific metadata is conveyed by other means. An endpoint MUST NOT generate an HTTP/2 message containing connection-specific header fields; any message containing connection-specific header fields MUST be treated as malformed (Section 8.1.2.6). > The only exception to this is the TE header field, which MAY be > present in an HTTP/2 request; when it is, it MUST NOT contain any > value other than "trailers". --- lib/http.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/http.js b/lib/http.js index 64e377c..3e14db2 100644 --- a/lib/http.js +++ b/lib/http.js @@ -155,7 +155,6 @@ var deprecatedHeaders = [ 'host', 'keep-alive', 'proxy-connection', - 'te', 'transfer-encoding', 'upgrade' ]; @@ -290,12 +289,13 @@ IncomingMessage.prototype._checkSpecialHeader = function _checkSpecialHeader(key IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) { // * An HTTP/2.0 request or response MUST NOT include any of the following header fields: - // Connection, Host, Keep-Alive, Proxy-Connection, TE, Transfer-Encoding, and Upgrade. A server + // Connection, Host, Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade. A server // MUST treat the presence of any of these header fields as a stream error of type // PROTOCOL_ERROR. + // If the TE header is present, it's only valid value is 'trailers' for (var i = 0; i < deprecatedHeaders.length; i++) { var key = deprecatedHeaders[i]; - if (key in headers) { + if (key in headers || (key === 'te' && headers[key] !== 'trailers')) { this._log.error({ key: key, value: headers[key] }, 'Deprecated header found'); this.stream.reset('PROTOCOL_ERROR'); return; From 3e7e90d00617f85df8d0361d4627a0d854ac31ea Mon Sep 17 00:00:00 2001 From: Tim Emiola Date: Sat, 22 Aug 2015 17:32:31 -0700 Subject: [PATCH 16/41] Avoid firing window_update unnecessarily. Currently, the following scenario can occur - a new connection is created; a new stream is created and a large payload is sent on it - the large payload exhausts its window and gets blocked - flow._read() calls .once('window_update', this._read) to be unblocked when a window update is received - at this point, the window_update event from the receipt of settings for the connection is received. This usually won't update the window, unless the remote window was much larger the original local window. However, it triggers the window_update event handler that was registered for unblocking the read - subsequently, the when WINDOW_UPDATE that actually update the window for stream is received, the read is no longer listening for it. This change resolves the problem by only firing the 'window_update' event if the window changes. This is a simple way of prevent the `SETTINGS receipt` window_update events taking up window_update events registered by reads --- lib/protocol/flow.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/protocol/flow.js b/lib/protocol/flow.js index ec4cdc4..687662e 100644 --- a/lib/protocol/flow.js +++ b/lib/protocol/flow.js @@ -323,7 +323,9 @@ Flow.prototype._increaseWindow = function _increaseWindow(size) { this._log.error('Flow control window grew too large.'); this.emit('error', 'FLOW_CONTROL_ERROR'); } else { - this.emit('window_update'); + if (size != 0) { + this.emit('window_update'); + } } } }; From 891464d9c14bb18430eaf26476180dfee21e925f Mon Sep 17 00:00:00 2001 From: Tim Emiola Date: Wed, 19 Aug 2015 02:17:56 -0700 Subject: [PATCH 17/41] Allow receipt of WINDOW_UPDATE on closed streams https://httpwg.github.io/specs/rfc7540.html#StreamStates 'WINDOW_UPDATE or RST_STREAM frames can be received in this state for a short period after a DATA or HEADERS frame containing an END_STREAM flag is sent.' --- lib/protocol/stream.js | 3 ++- test/stream.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/protocol/stream.js b/lib/protocol/stream.js index c42de87..2d1f16a 100644 --- a/lib/protocol/stream.js +++ b/lib/protocol/stream.js @@ -567,8 +567,9 @@ Stream.prototype._transition = function transition(sending, frame) { // can be used to close any of those streams. case 'CLOSED': if (PRIORITY || (sending && RST_STREAM) || + (receiving && WINDOW_UPDATE) || (receiving && this._closedByUs && - (this._closedWithRst || WINDOW_UPDATE || RST_STREAM || ALTSVC))) { + (this._closedWithRst || RST_STREAM || ALTSVC))) { /* No state change */ } else { streamError = 'STREAM_CLOSED'; diff --git a/test/stream.js b/test/stream.js index 87af55f..90e0ef6 100644 --- a/test/stream.js +++ b/test/stream.js @@ -192,7 +192,7 @@ describe('stream.js', function() { stream.headers({}); stream.end(); stream.upstream.write({ type: 'HEADERS', headers:{}, flags: { END_STREAM: true }, count_change: util.noop }); - example_frames.slice(1).forEach(function(invalid_frame) { + example_frames.slice(2).forEach(function(invalid_frame) { invalid_frame.count_change = util.noop; expect(stream._transition.bind(stream, false, invalid_frame)).to.throw('Uncaught, unspecified "error" event.'); }); From c5392bd06ecf22ce948664721b8a31203e2a1d4c Mon Sep 17 00:00:00 2001 From: Mike Frey Date: Wed, 20 Jan 2016 07:53:56 -0600 Subject: [PATCH 18/41] adds address function for compatibility with core http and https modules --- lib/http.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/http.js b/lib/http.js index 64e377c..8b4459f 100644 --- a/lib/http.js +++ b/lib/http.js @@ -583,6 +583,10 @@ Server.prototype.addContext = function addContext(hostname, credentials) { } }; +Server.prototype.address = function address() { + return this._server.address() +}; + function createServerRaw(options, requestListener) { if (typeof options === 'function') { requestListener = options; From 1d76d2699f302fead00bc888cd316e873f982986 Mon Sep 17 00:00:00 2001 From: Tim Emiola Date: Thu, 28 Jan 2016 10:43:14 -0800 Subject: [PATCH 19/41] Update LICENSE, add Google inc This covers the contributions in PRs #170, #171 and #172 --- LICENSE | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 55c4878..9bb2e9c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (C) 2013 Gábor Molnár +Copyright (C) 2013 Gábor Molnár , Google Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -20,4 +20,3 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - From f0b9a5e9bd9dc1a4b5b4da99d51a1182707b5ecd Mon Sep 17 00:00:00 2001 From: Artem Salpagarov Date: Mon, 8 Feb 2016 18:02:58 +0300 Subject: [PATCH 20/41] Adding Agent.destroy as in node http(s) module --- lib/http.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/http.js b/lib/http.js index 7c6d753..63561fb 100644 --- a/lib/http.js +++ b/lib/http.js @@ -1009,6 +1009,15 @@ Agent.prototype.get = function get(options, callback) { return request; }; +Agent.prototype.destroy = function(error) { + if (this._httpsAgent) { + this._httpsAgent.destroy(); + } + for (var key in this.endpoints) { + this.endpoints[key].close(error); + } +}; + function unbundleSocket(socket) { socket.removeAllListeners('data'); socket.removeAllListeners('end'); From 7b2c75160908b604aeece5d7c8b85777b1e7e940 Mon Sep 17 00:00:00 2001 From: Scott Davis Date: Thu, 11 Feb 2016 13:36:35 -0800 Subject: [PATCH 21/41] Ability to handle FRAME_SIZE_ERROR gracefully as a connection error. (happens frequently with NO_TLS connection). --- lib/http.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/http.js b/lib/http.js index 7c6d753..853dfac 100644 --- a/lib/http.js +++ b/lib/http.js @@ -927,6 +927,11 @@ Agent.prototype.request = function request(options, callback) { request.emit('error', error); }); + endpoint.on('error', function(error){ + self._log.error('Connection error: ' + error.toString()); + request.emit('error', error); + }); + this.endpoints[key] = endpoint; endpoint.pipe(endpoint.socket).pipe(endpoint); request._start(endpoint.createStream(), options); From c5a5d95b095e08169afdf277b064bbf2c1fc2ad7 Mon Sep 17 00:00:00 2001 From: Thomas Watson Steen Date: Sun, 21 Feb 2016 17:54:52 +0100 Subject: [PATCH 22/41] Ensure set-cookie headers are always an array To be API compatible with Node core, http2 should enforce that the set-cookie header is always an Array. Even if there is only one element in it. For reference here's the equivilant in the Node core source code: https://github.com/nodejs/node/blob/fab240a886b69ef9fa78573fc210c15cfe0018f0/lib/_http_incoming.js#L127-L133 --- lib/http.js | 6 +++++- test/http.js | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/http.js b/lib/http.js index 7c6d753..0b13c76 100644 --- a/lib/http.js +++ b/lib/http.js @@ -261,7 +261,11 @@ IncomingMessage.prototype._onHeaders = function _onHeaders(headers) { // * Store the _regular_ headers in `this.headers` for (var name in headers) { if (name[0] !== ':') { - this.headers[name] = headers[name]; + if (name === 'set-cookie' && !Array.isArray(headers[name])) { + this.headers[name] = [headers[name]]; + } else { + this.headers[name] = headers[name]; + } } } diff --git a/test/http.js b/test/http.js index efd0f39..95a074e 100644 --- a/test/http.js +++ b/test/http.js @@ -349,6 +349,9 @@ describe('http.js', function() { response.removeHeader('nonexistent'); expect(response.getHeader('nonexistent')).to.equal(undefined); + // A set-cookie header which should always be an array + response.setHeader('set-cookie', 'foo'); + // Don't send date response.sendDate = false; @@ -375,6 +378,8 @@ describe('http.js', function() { request.on('response', function(response) { expect(response.headers[headerName]).to.equal(headerValue); expect(response.headers['nonexistent']).to.equal(undefined); + expect(response.headers['set-cookie']).to.an.instanceof(Array) + expect(response.headers['set-cookie']).to.deep.equal(['foo']) expect(response.headers['date']).to.equal(undefined); response.on('data', function(data) { expect(data.toString()).to.equal(message); From 0bd6cace6e892f8d300e0cfebc9b4a13cf44dc4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9licien=20Fran=C3=A7ois?= Date: Wed, 24 Feb 2016 17:58:27 +0100 Subject: [PATCH 23/41] emit error events instead of throwing Error Done for all operation that may be done asynchronously or in a nested way, in an EventEmitter context. The change allow to handle errors (by catching error events) while keeping the original behavior (throwing error) if error events are not listened --- lib/http.js | 8 ++++---- lib/protocol/stream.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/http.js b/lib/http.js index 7c6d753..b98c30c 100644 --- a/lib/http.js +++ b/lib/http.js @@ -360,11 +360,11 @@ OutgoingMessage.prototype._finish = function _finish() { OutgoingMessage.prototype.setHeader = function setHeader(name, value) { if (this.headersSent) { - throw new Error('Can\'t set headers after they are sent.'); + return this.emit('error', new Error('Can\'t set headers after they are sent.')); } else { name = name.toLowerCase(); if (deprecatedHeaders.indexOf(name) !== -1) { - throw new Error('Cannot set deprecated header: ' + name); + return this.emit('error', new Error('Cannot set deprecated header: ' + name)); } this._headers[name] = value; } @@ -372,7 +372,7 @@ OutgoingMessage.prototype.setHeader = function setHeader(name, value) { OutgoingMessage.prototype.removeHeader = function removeHeader(name) { if (this.headersSent) { - throw new Error('Can\'t remove headers after they are sent.'); + return this.emit('error', new Error('Can\'t remove headers after they are sent.')); } else { delete this._headers[name.toLowerCase()]; } @@ -891,7 +891,7 @@ Agent.prototype.request = function request(options, callback) { if (!options.plain && options.protocol === 'http:') { this._log.error('Trying to negotiate client request with Upgrade from HTTP/1.1'); - throw new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.'); + this.emit('error', new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.')); } var request = new OutgoingRequest(this._log); diff --git a/lib/protocol/stream.js b/lib/protocol/stream.js index 2d1f16a..b8c3cc4 100644 --- a/lib/protocol/stream.js +++ b/lib/protocol/stream.js @@ -625,7 +625,7 @@ Stream.prototype._transition = function transition(sending, frame) { // * When sending something invalid, throwing an exception, since it is probably a bug. if (sending) { this._log.error(info, 'Sending illegal frame.'); - throw new Error('Sending illegal frame (' + frame.type + ') in ' + this.state + ' state.'); + return this.emit('error', new Error('Sending illegal frame (' + frame.type + ') in ' + this.state + ' state.')); } // * In case of a serious problem, emitting and error and letting someone else handle it From b5546f4fa2672cb32243fd26785da58ef0b0a6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9licien=20Fran=C3=A7ois?= Date: Wed, 24 Feb 2016 21:38:15 +0100 Subject: [PATCH 24/41] set agent max listeners to infinite --- lib/http.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/http.js b/lib/http.js index 7c6d753..98d9a2d 100644 --- a/lib/http.js +++ b/lib/http.js @@ -856,6 +856,7 @@ function getTLS(options, callback) { function Agent(options) { EventEmitter.call(this); + this.setMaxListeners(0); options = util._extend({}, options); From bf9be484b1b14a3c478d5beb442b8a87c4ac77a1 Mon Sep 17 00:00:00 2001 From: Ike Peters Date: Wed, 2 Mar 2016 13:51:19 -0800 Subject: [PATCH 25/41] removing unnecessary files and directories from npm pkg removing unnecessary files and directories from npm pkg removing unnecessary files and directories from npm pkg --- .npmignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..efab07f --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +test +.travis.yml From ee4613bb7760847f6954901a6cac53a9546d317c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Holy=CC=81?= Date: Wed, 30 Mar 2016 15:06:30 +0200 Subject: [PATCH 26/41] implicitHeaders alias implicitHeader alias for implicitHeaders to be compatible with Node.js --- lib/http.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/http.js b/lib/http.js index 7c6d753..6772d9f 100644 --- a/lib/http.js +++ b/lib/http.js @@ -741,6 +741,10 @@ OutgoingResponse.prototype._implicitHeaders = function _implicitHeaders() { } }; +OutgoingResponse.prototype._implicitHeader = function() { + this._implicitHeaders(); +}; + OutgoingResponse.prototype.write = function write() { this._implicitHeaders(); return OutgoingMessage.prototype.write.apply(this, arguments); From c720a9fc7c500fdddfb00c716a3145309029f1f4 Mon Sep 17 00:00:00 2001 From: Jhen Date: Sat, 2 Apr 2016 18:01:19 +0800 Subject: [PATCH 27/41] Assign socket to response --- lib/http.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http.js b/lib/http.js index 7c6d753..69efa6e 100644 --- a/lib/http.js +++ b/lib/http.js @@ -503,7 +503,7 @@ Server.prototype._start = function _start(socket) { // Some conformance to Node.js Https specs allows to distinguish clients: request.remoteAddress = socket.remoteAddress; request.remotePort = socket.remotePort; - request.connection = request.socket = socket; + request.connection = request.socket = response.socket = socket; request.once('ready', self.emit.bind(self, 'request', request, response)); }); From d9f41815704a634d5b893fb861dd6e7b5b73233c Mon Sep 17 00:00:00 2001 From: 5lava Date: Sat, 16 Apr 2016 03:54:18 -0400 Subject: [PATCH 28/41] Leak less listeners on error. --- lib/http.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/http.js b/lib/http.js index 7c6d753..a00297b 100644 --- a/lib/http.js +++ b/lib/http.js @@ -949,6 +949,7 @@ Agent.prototype.request = function request(options, callback) { httpsRequest.on('error', function (error) { self._log.error('Socket error: ' + error.toString()); + self.removeAllListeners(key); request.emit('error', error); }); From daa7e056081cb9d3fed151374409afcdaeebee26 Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Thu, 21 Apr 2016 15:15:24 -0700 Subject: [PATCH 29/41] Release v3.3.3 --- HISTORY.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 1175321..49562d6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,10 @@ Version history =============== +### 3.3.3 (2016-04-21) ### + +* Bugfixes from pull requests (https://github.com/molnarg/node-http2/search?q=milestone%3Av3.3.3&type=Issues&utf8=%E2%9C%93) + ### 3.3.2 (2016-01-11) ### * Fix an incompatibility with Firefox (issue 167) diff --git a/package.json b/package.json index 8fe739f..9fe2b80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "http2", - "version": "3.3.2", + "version": "3.3.3", "description": "An HTTP/2 client and server implementation", "main": "lib/index.js", "engines" : { From 202d32edc28165178a489b4b18d75621edf96249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9licien=20FRANCOIS?= Date: Fri, 22 Apr 2016 20:28:22 +0200 Subject: [PATCH 30/41] Fix http2 agent doesn't work with request module Fix #174 --- lib/http.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/http.js b/lib/http.js index 94e4c4b..3a2ee1c 100644 --- a/lib/http.js +++ b/lib/http.js @@ -834,7 +834,12 @@ function requestTLS(options, callback) { if (options.protocol && options.protocol !== "https:") { throw new Error('This interface only supports https-schemed URLs'); } - return (options.agent || exports.globalAgent).request(options, callback); + if (options.agent && typeof(options.agent.request) === 'function') { + var agentOptions = util._extend({}, options); + delete agentOptions.agent; + return options.agent.request(agentOptions, callback); + } + return exports.globalAgent.request(options, callback); } function getRaw(options, callback) { From 3ea0e593d0d84f428fe81c109422a01a95309527 Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Fri, 22 Apr 2016 12:26:17 -0700 Subject: [PATCH 31/41] Ignore VS Code project folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7904d2a..da9d5e5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules .idea coverage doc +.vscode From 074fdd35476a1fcbf49f8ee4252939bdafc76ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9licien=20FRANCOIS?= Date: Fri, 22 Apr 2016 22:48:05 +0200 Subject: [PATCH 32/41] Fix http2 agent doesn't work with http2.get --- lib/http.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/http.js b/lib/http.js index 3a2ee1c..e9cbed3 100644 --- a/lib/http.js +++ b/lib/http.js @@ -861,7 +861,12 @@ function getTLS(options, callback) { if (options.protocol && options.protocol !== "https:") { throw new Error('This interface only supports https-schemed URLs'); } - return (options.agent || exports.globalAgent).get(options, callback); + if (options.agent && typeof(options.agent.get) === 'function') { + var agentOptions = util._extend({}, options); + delete agentOptions.agent; + return options.agent.get(agentOptions, callback); + } + return exports.globalAgent.get(options, callback); } // Agent class From 5c25fe81725d7ab5e19496e318964da9a8b20b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9licien=20FRANCOIS?= Date: Fri, 22 Apr 2016 22:58:27 +0200 Subject: [PATCH 33/41] Filter also agent option in raw mode --- lib/http.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/http.js b/lib/http.js index e9cbed3..939f63f 100644 --- a/lib/http.js +++ b/lib/http.js @@ -823,7 +823,12 @@ function requestRaw(options, callback) { if (options.protocol && options.protocol !== "http:") { throw new Error('This interface only supports http-schemed URLs'); } - return (options.agent || exports.globalAgent).request(options, callback); + if (options.agent && typeof(options.agent.request) === 'function') { + var agentOptions = util._extend({}, options); + delete agentOptions.agent; + return options.agent.request(agentOptions, callback); + } + return exports.globalAgent.request(options, callback); } function requestTLS(options, callback) { @@ -850,7 +855,12 @@ function getRaw(options, callback) { if (options.protocol && options.protocol !== "http:") { throw new Error('This interface only supports http-schemed URLs'); } - return (options.agent || exports.globalAgent).get(options, callback); + if (options.agent && typeof(options.agent.get) === 'function') { + var agentOptions = util._extend({}, options); + delete agentOptions.agent; + return options.agent.get(agentOptions, callback); + } + return exports.globalAgent.get(options, callback); } function getTLS(options, callback) { From 6e357416dbefa37e73b3e9c54404021df34a0a73 Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Fri, 22 Apr 2016 15:39:07 -0700 Subject: [PATCH 34/41] Release v3.3.4 --- HISTORY.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 49562d6..37dfed4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,9 @@ Version history =============== +### 3.3.4 (2016-04-22) ### +* More PR bugfixes (https://github.com/molnarg/node-http2/issues?q=milestone%3Av3.3.4) + ### 3.3.3 (2016-04-21) ### * Bugfixes from pull requests (https://github.com/molnarg/node-http2/search?q=milestone%3Av3.3.3&type=Issues&utf8=%E2%9C%93) diff --git a/package.json b/package.json index 9fe2b80..e774c7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "http2", - "version": "3.3.3", + "version": "3.3.4", "description": "An HTTP/2 client and server implementation", "main": "lib/index.js", "engines" : { From 8a0869b484d0d9d0d8535bb03d9919bcdb6a45ae Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Mon, 1 Aug 2016 10:03:35 -0700 Subject: [PATCH 35/41] Update links in documentation --- lib/http.js | 49 ++++++++++++++++---------------------- lib/index.js | 10 ++++---- lib/protocol/compressor.js | 27 ++++++++++----------- lib/protocol/flow.js | 10 ++++---- lib/protocol/framer.js | 36 ++++++++++++++-------------- lib/protocol/index.js | 21 ++++++++-------- lib/protocol/stream.js | 10 ++++---- package.json | 2 +- 8 files changed, 78 insertions(+), 87 deletions(-) diff --git a/lib/http.js b/lib/http.js index d71513d..4c4234c 100644 --- a/lib/http.js +++ b/lib/http.js @@ -11,17 +11,15 @@ // ------------------------------------ // // - **Class: http2.Endpoint**: an API for using the raw HTTP/2 framing layer. For documentation -// see the [lib/endpoint.js](endpoint.html) file. +// see [protocol/endpoint.js](protocol/endpoint.html). // // - **Class: http2.Server** // - **Event: 'connection' (socket, [endpoint])**: there's a second argument if the negotiation of -// HTTP/2 was successful: the reference to the [Endpoint](endpoint.html) object tied to the +// HTTP/2 was successful: the reference to the [Endpoint](protocol/endpoint.html) object tied to the // socket. // // - **http2.createServer(options, [requestListener])**: additional option: // - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object -// - **plain**: if `true`, the server will accept HTTP/2 connections over plain TCP instead of -// TLS // // - **Class: http2.ServerResponse** // - **response.push(options)**: initiates a server push. `options` describes the 'imaginary' @@ -33,20 +31,17 @@ // - **new Agent(options)**: additional option: // - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object // - **agent.sockets**: only contains TCP sockets that corresponds to HTTP/1 requests. -// - **agent.endpoints**: contains [Endpoint](endpoint.html) objects for HTTP/2 connections. +// - **agent.endpoints**: contains [Endpoint](protocol/endpoint.html) objects for HTTP/2 connections. // -// - **http2.request(options, [callback])**: additional option: -// - **plain**: if `true`, the client will not try to build a TLS tunnel, instead it will use -// the raw TCP stream for HTTP/2 +// - **http2.request(options, [callback])**: +// - similar to http.request // -// - **http2.get(options, [callback])**: additional option: -// - **plain**: if `true`, the client will not try to build a TLS tunnel, instead it will use -// the raw TCP stream for HTTP/2 -// - this performs request and then also calls request.end() for request instance +// - **http2.get(options, [callback])**: +// - similar to http.get // // - **Class: http2.ClientRequest** // - **Event: 'socket' (socket)**: in case of an HTTP/2 incoming message, `socket` is a reference -// to the associated [HTTP/2 Stream](stream.html) object (and not to the TCP socket). +// to the associated [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket). // - **Event: 'push' (promise)**: signals the intention of a server push associated to this // request. `promise` is an IncomingPromise. If there's no listener for this event, the server // push is cancelled. @@ -57,7 +52,7 @@ // - has two subclasses for easier interface description: **IncomingRequest** and // **IncomingResponse** // - **message.socket**: in case of an HTTP/2 incoming message, it's a reference to the associated -// [HTTP/2 Stream](stream.html) object (and not to the TCP socket). +// [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket). // // - **Class: http2.IncomingRequest (IncomingMessage)** // - **message.url**: in case of an HTTP/2 incoming request, the `url` field always contains the @@ -92,11 +87,11 @@ // but will function normally when falling back to using HTTP/1.1. // // - **Class: http2.Server** -// - **Event: 'checkContinue'**: not in the spec, yet (see [http-spec#18][expect-continue]) +// - **Event: 'checkContinue'**: not in the spec // - **Event: 'upgrade'**: upgrade is deprecated in HTTP/2 // - **Event: 'timeout'**: HTTP/2 sockets won't timeout because of application level keepalive // (PING frames) -// - **Event: 'connect'**: not in the spec, yet (see [http-spec#230][connect]) +// - **Event: 'connect'**: not yet supported // - **server.setTimeout(msecs, [callback])** // - **server.timeout** // @@ -124,11 +119,9 @@ // - **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-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 +// [1]: https://nodejs.org/api/https.html +// [2]: https://nodejs.org/api/http.html +// [3]: https://tools.ietf.org/html/rfc7540#section-8.1.2.4 // Common server and client side code // ================================== @@ -162,7 +155,7 @@ var deprecatedHeaders = [ // When doing NPN/ALPN negotiation, HTTP/1.1 is used as fallback var supportedProtocols = [protocol.VERSION, 'http/1.1', 'http/1.0']; -// Ciphersuite list based on the recommendations of http://wiki.mozilla.org/Security/Server_Side_TLS +// Ciphersuite list based on the recommendations of https://wiki.mozilla.org/Security/Server_Side_TLS // The only modification is that kEDH+AESGCM were placed after DHE and ECDHE suites var cipherSuites = [ 'ECDHE-RSA-AES128-GCM-SHA256', @@ -226,7 +219,7 @@ exports.serializers = protocol.serializers; // --------------------- function IncomingMessage(stream) { - // * This is basically a read-only wrapper for the [Stream](stream.html) class. + // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class. PassThrough.call(this); stream.pipe(this); this.socket = this.stream = stream; @@ -250,7 +243,7 @@ function IncomingMessage(stream) { } IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } }); -// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.3) +// [Request Header Fields](https://tools.ietf.org/html/rfc7540#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. @@ -326,7 +319,7 @@ IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) // --------------------- function OutgoingMessage() { - // * This is basically a read-only wrapper for the [Stream](stream.html) class. + // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class. Writable.call(this); this._headers = {}; @@ -535,7 +528,7 @@ Server.prototype._fallback = function _fallback(socket) { // There are [3 possible signatures][1] of the `listen` function. Every arguments is forwarded to // the backing TCP or HTTPS server. -// [1]: http://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback +// [1]: https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback Server.prototype.listen = function listen(port, hostname) { this._log.info({ on: ((typeof hostname === 'string') ? (hostname + ':' + port) : port) }, 'Listening for incoming connections'); @@ -659,7 +652,7 @@ function IncomingRequest(stream) { } IncomingRequest.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingRequest } }); -// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.3) +// [Request Header Fields](https://tools.ietf.org/html/rfc7540#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. @@ -1214,7 +1207,7 @@ function IncomingResponse(stream) { } IncomingResponse.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingResponse } }); -// [Response Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.4) +// [Response Header Fields](https://tools.ietf.org/html/rfc7540#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. diff --git a/lib/index.js b/lib/index.js index 60981c2..c67883d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,4 +1,4 @@ -// [node-http2][homepage] is an [HTTP/2 (draft 16)][http2] implementation for [node.js][node]. +// [node-http2][homepage] is an [HTTP/2][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: @@ -10,10 +10,10 @@ // (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-16 -// [node]: http://nodejs.org/ -// [node-https]: http://nodejs.org/api/https.html -// [node-http]: http://nodejs.org/api/http.html +// [http2]: https://tools.ietf.org/html/rfc7540 +// [node]: https://nodejs.org/ +// [node-https]: https://nodejs.org/api/https.html +// [node-http]: https://nodejs.org/api/http.html module.exports = require('./http'); diff --git a/lib/protocol/compressor.js b/lib/protocol/compressor.js index 5aacae5..f1d1795 100644 --- a/lib/protocol/compressor.js +++ b/lib/protocol/compressor.js @@ -11,9 +11,9 @@ // provide a layer between the [framer](framer.html) and the // [connection handling component](connection.html). // -// [node-transform]: http://nodejs.org/api/stream.html#stream_class_stream_transform -// [node-objectmode]: http://nodejs.org/api/stream.html#stream_new_stream_readable_options -// [http2-compression]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07 +// [node-transform]: https://nodejs.org/api/stream.html#stream_class_stream_transform +// [node-objectmode]: https://nodejs.org/api/stream.html#stream_new_stream_readable_options +// [http2-compression]: https://tools.ietf.org/html/rfc7541 exports.HeaderTable = HeaderTable; exports.HuffmanTable = HuffmanTable; @@ -35,8 +35,8 @@ var util = require('util'); // The [Header Table] is a component used to associate headers to index values. It is basically an // ordered list of `[name, value]` pairs, so it's implemented as a subclass of `Array`. // In this implementation, the Header Table and the [Static Table] are handled as a single table. -// [Header Table]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.1.2 -// [Static Table]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B +// [Header Table]: https://tools.ietf.org/html/rfc7541#section-2.3.2 +// [Static Table]: https://tools.ietf.org/html/rfc7541#section-2.3.1 function HeaderTable(log, limit) { var self = HeaderTable.staticTable.map(entryFromPair); self._log = log; @@ -70,7 +70,7 @@ function size(entry) { } // The `add(index, entry)` can be used to [manage the header table][tablemgmt]: -// [tablemgmt]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.3 +// [tablemgmt]: https://tools.ietf.org/html/rfc7541#section-4 // // * it pushes the new `entry` at the beggining of the table // * before doing such a modification, it has to be ensured that the header table size will stay @@ -115,9 +115,8 @@ HeaderTable.prototype.setSizeLimit = function setSizeLimit(limit) { this._enforceLimit(this._limit); }; -// [The Static Table](http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B) +// [The Static Table](https://tools.ietf.org/html/rfc7541#section-2.3.1) // ------------------ -// [statictable]:http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B // The table is generated with feeding the table from the spec to the following sed command: // @@ -208,14 +207,14 @@ function HeaderSetDecompressor(log, table) { // `_transform` is the implementation of the [corresponding virtual function][_transform] of the // TransformStream class. It collects the data chunks for later processing. -// [_transform]: http://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback +// [_transform]: https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback HeaderSetDecompressor.prototype._transform = function _transform(chunk, encoding, callback) { this._chunks.push(chunk); callback(); }; // `execute(rep)` executes the given [header representation][representation]. -// [representation]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.1.4 +// [representation]: https://tools.ietf.org/html/rfc7541#section-6 // The *JavaScript object representation* of a header representation: // @@ -281,7 +280,7 @@ HeaderSetDecompressor.prototype._execute = function _execute(rep) { // `_flush` is the implementation of the [corresponding virtual function][_flush] of the // TransformStream class. The whole decompressing process is done in `_flush`. It gets called when // the input stream is over. -// [_flush]: http://nodejs.org/api/stream.html#stream_transform_flush_callback +// [_flush]: https://nodejs.org/api/stream.html#stream_transform_flush_callback HeaderSetDecompressor.prototype._flush = function _flush(callback) { var buffer = concat(this._chunks); @@ -327,7 +326,7 @@ HeaderSetCompressor.prototype.send = function send(rep) { // `_transform` is the implementation of the [corresponding virtual function][_transform] of the // TransformStream class. It processes the input headers one by one: -// [_transform]: http://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback +// [_transform]: https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback HeaderSetCompressor.prototype._transform = function _transform(pair, encoding, callback) { var name = pair[0].toLowerCase(); var value = pair[1]; @@ -373,12 +372,12 @@ HeaderSetCompressor.prototype._transform = function _transform(pair, encoding, c // `_flush` is the implementation of the [corresponding virtual function][_flush] of the // TransformStream class. It gets called when there's no more header to compress. The final step: -// [_flush]: http://nodejs.org/api/stream.html#stream_transform_flush_callback +// [_flush]: https://nodejs.org/api/stream.html#stream_transform_flush_callback HeaderSetCompressor.prototype._flush = function _flush(callback) { callback(); }; -// [Detailed Format](http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-4) +// [Detailed Format](https://tools.ietf.org/html/rfc7541#section-5) // ----------------- // ### Integer representation ### diff --git a/lib/protocol/flow.js b/lib/protocol/flow.js index 687662e..1647807 100644 --- a/lib/protocol/flow.js +++ b/lib/protocol/flow.js @@ -5,7 +5,7 @@ var assert = require('assert'); // Flow is a [Duplex stream][1] subclass which implements HTTP/2 flow control. It is designed to be // subclassed by [Connection](connection.html) and the `upstream` component of [Stream](stream.html). -// [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex +// [1]: https://nodejs.org/api/stream.html#stream_class_stream_duplex var Duplex = require('stream').Duplex; @@ -19,7 +19,7 @@ exports.Flow = Flow; // * **setInitialWindow(size)**: the initial flow control window size can be changed *any time* // ([as described in the standard][1]) using this method // -// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.9.2 +// [1]: https://tools.ietf.org/html/rfc7540#section-6.9.2 // API for child classes // --------------------- @@ -42,7 +42,7 @@ exports.Flow = Flow; // * **read(limit): frame**: like the regular `read`, but the 'flow control size' (0 for non-DATA // frames, length of the payload for DATA frames) of the returned frame will be under `limit`. // Small exception: pass -1 as `limit` if the max. flow control size is 0. `read(0)` means the -// same thing as [in the original API](http://nodejs.org/api/stream.html#stream_stream_read_0). +// same thing as [in the original API](https://nodejs.org/api/stream.html#stream_stream_read_0). // // * **getLastQueuedFrame(): frame**: returns the last frame in output buffers // @@ -79,7 +79,7 @@ Flow.prototype._receive = function _receive(frame, callback) { // `_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 +// [1]: https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1 Flow.prototype._write = function _write(frame, encoding, callback) { var sentToUs = (this._flowControlId === undefined) || (frame.stream === this._flowControlId); @@ -147,7 +147,7 @@ Flow.prototype._send = function _send() { // `_send` is called by `_read` which is in turn [called by Duplex][1] when it wants to have more // items in the output queue. -// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1 +// [1]: https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1 Flow.prototype._read = function _read() { // * if the flow control queue is empty, then let the user push more frames if (this._queue.length === 0) { diff --git a/lib/protocol/framer.js b/lib/protocol/framer.js index ca29221..244e60a 100644 --- a/lib/protocol/framer.js +++ b/lib/protocol/framer.js @@ -1,7 +1,7 @@ // The framer consists of two [Transform Stream][1] subclasses that operate in [object mode][2]: // the Serializer and the Deserializer -// [1]: http://nodejs.org/api/stream.html#stream_class_stream_transform -// [2]: http://nodejs.org/api/stream.html#stream_new_stream_readable_options +// [1]: https://nodejs.org/api/stream.html#stream_class_stream_transform +// [2]: https://nodejs.org/api/stream.html#stream_new_stream_readable_options var assert = require('assert'); var Transform = require('stream').Transform; @@ -146,10 +146,10 @@ Deserializer.prototype._transform = function _transform(chunk, encoding, done) { done(); }; -// [Frame Header](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1) +// [Frame Header](https://tools.ietf.org/html/rfc7540#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 +// HTTP/2 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 @@ -273,7 +273,7 @@ Deserializer.commonHeader = function readCommonHeader(buffer, frame) { // * `typeSpecificAttributes`: a register of frame specific frame object attributes (used by // logging code and also serves as documentation for frame objects) -// [DATA Frames](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.1) +// [DATA Frames](https://tools.ietf.org/html/rfc7540#section-6.1) // ------------------------------------------------------------ // // DATA frames (type=0x0) convey arbitrary, variable-length sequences of octets associated with a @@ -320,7 +320,7 @@ Deserializer.DATA = function readData(buffer, frame) { } }; -// [HEADERS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.2) +// [HEADERS](https://tools.ietf.org/html/rfc7540#section-6.2) // -------------------------------------------------------------- // // The HEADERS frame (type=0x1) allows the sender to create a stream. @@ -418,7 +418,7 @@ Deserializer.HEADERS = function readHeadersPriority(buffer, frame) { } }; -// [PRIORITY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.3) +// [PRIORITY](https://tools.ietf.org/html/rfc7540#section-6.3) // ------------------------------------------------------- // // The PRIORITY frame (type=0x2) specifies the sender-advised priority of a stream. @@ -467,7 +467,7 @@ Deserializer.PRIORITY = function readPriority(buffer, frame) { frame.priorityWeight = buffer.readUInt8(4); }; -// [RST_STREAM](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.4) +// [RST_STREAM](https://tools.ietf.org/html/rfc7540#section-6.4) // ----------------------------------------------------------- // // The RST_STREAM frame (type=0x3) allows for abnormal termination of a stream. @@ -509,7 +509,7 @@ Deserializer.RST_STREAM = function readRstStream(buffer, frame) { } }; -// [SETTINGS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.5) +// [SETTINGS](https://tools.ietf.org/html/rfc7540#section-6.5) // ------------------------------------------------------- // // The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints @@ -616,7 +616,7 @@ definedSettings[4] = { name: 'SETTINGS_INITIAL_WINDOW_SIZE', flag: false }; // 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-16#section-6.6) +// [PUSH_PROMISE](https://tools.ietf.org/html/rfc7540#section-6.6) // --------------------------------------------------------------- // // The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of streams the @@ -686,7 +686,7 @@ Deserializer.PUSH_PROMISE = function readPushPromise(buffer, frame) { } }; -// [PING](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.7) +// [PING](https://tools.ietf.org/html/rfc7540#section-6.7) // ----------------------------------------------- // // The PING frame (type=0x6) is a mechanism for measuring a minimal round-trip time from the @@ -716,7 +716,7 @@ Deserializer.PING = function readPing(buffer, frame) { frame.data = buffer; }; -// [GOAWAY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.8) +// [GOAWAY](https://tools.ietf.org/html/rfc7540#section-6.8) // --------------------------------------------------- // // The GOAWAY frame (type=0x7) informs the remote peer to stop creating streams on this connection. @@ -771,7 +771,7 @@ Deserializer.GOAWAY = function readGoaway(buffer, frame) { } }; -// [WINDOW_UPDATE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.9) +// [WINDOW_UPDATE](https://tools.ietf.org/html/rfc7540#section-6.9) // ----------------------------------------------------------------- // // The WINDOW_UPDATE frame (type=0x8) is used to implement flow control. @@ -809,7 +809,7 @@ Deserializer.WINDOW_UPDATE = function readWindowUpdate(buffer, frame) { } }; -// [CONTINUATION](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.10) +// [CONTINUATION](https://tools.ietf.org/html/rfc7540#section-6.10) // ------------------------------------------------------------ // // The CONTINUATION frame (type=0x9) is used to continue a sequence of header block fragments. @@ -834,7 +834,7 @@ Deserializer.CONTINUATION = function readContinuation(buffer, frame) { frame.data = buffer; }; -// [ALTSVC](http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06#section-4) +// [ALTSVC](https://tools.ietf.org/html/rfc7838#section-4) // ------------------------------------------------------------ // // The ALTSVC frame (type=0xA) advertises the availability of an alternative service to the client. @@ -859,13 +859,13 @@ frameFlags.ALTSVC = []; // octets, of the Origin field. // // Origin: An OPTIONAL sequence of characters containing ASCII -// serialisation of an origin ([RFC6454](http://tools.ietf.org/html/rfc6454), +// serialisation of an origin ([RFC6454](https://tools.ietf.org/html/rfc6454), // Section 6.2) that the alternate service is applicable to. // // Alt-Svc-Field-Value: A sequence of octets (length determined by // subtracting the length of all preceding fields from the frame // length) containing a value identical to the Alt-Svc field value -// defined in (Section 3)[http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06#section-3] +// defined in (Section 3)[https://tools.ietf.org/html/rfc7838#section-3] // (ABNF production "Alt-Svc"). typeSpecificAttributes.ALTSVC = ['maxAge', 'port', 'protocolID', 'host', @@ -1089,7 +1089,7 @@ Serializer.BLOCKED = function writeBlocked(frame, buffers) { Deserializer.BLOCKED = function readBlocked(buffer, frame) { }; -// [Error Codes](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-7) +// [Error Codes](https://tools.ietf.org/html/rfc7540#section-7) // ------------------------------------------------------------ var errorCodes = [ diff --git a/lib/protocol/index.js b/lib/protocol/index.js index 39f48f4..0f3720e 100644 --- a/lib/protocol/index.js +++ b/lib/protocol/index.js @@ -1,4 +1,4 @@ -// [node-http2-protocol][homepage] is an implementation of the [HTTP/2 (draft 16)][http2] +// This is an implementation of the [HTTP/2][http2] // framing layer for [node.js][node]. // // The main building blocks are [node.js streams][node-stream] that are connected through pipes. @@ -14,7 +14,7 @@ // lifecycle and settings, and responsible for enforcing the connection level limits (flow // control, initiated stream limit) // -// * [Stream](stream.html): implementation of the [HTTP/2 stream concept](http2-stream). +// * [Stream](stream.html): implementation of the [HTTP/2 stream concept][http2-stream]. // Implements the [stream state machine][http2-streamstate] defined by the standard, provides // management methods and events for using the stream (sending/receiving headers, data, etc.), // and enforces stream level constraints (flow control, sending only legal frames). @@ -27,15 +27,14 @@ // * [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-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 +// [http2]: https://tools.ietf.org/html/rfc7540 +// [http2-connheader]: https://tools.ietf.org/html/rfc7540#section-3.5 +// [http2-stream]: https://tools.ietf.org/html/rfc7540#section-5 +// [http2-streamstate]: https://tools.ietf.org/html/rfc7540#section-5.1 +// [node]: https://nodejs.org/ +// [node-stream]: https://nodejs.org/api/stream.html +// [node-https]: https://nodejs.org/api/https.html +// [node-http]: https://nodejs.org/api/http.html exports.VERSION = 'h2'; diff --git a/lib/protocol/stream.js b/lib/protocol/stream.js index b8c3cc4..6d520b9 100644 --- a/lib/protocol/stream.js +++ b/lib/protocol/stream.js @@ -3,8 +3,8 @@ var assert = require('assert'); // The Stream class // ================ -// Stream is a [Duplex stream](http://nodejs.org/api/stream.html#stream_class_stream_duplex) -// subclass that implements the [HTTP/2 Stream](http://http2.github.io/http2-spec/#rfc.section.3.4) +// Stream is a [Duplex stream](https://nodejs.org/api/stream.html#stream_class_stream_duplex) +// subclass that implements the [HTTP/2 Stream](https://tools.ietf.org/html/rfc7540#section-5) // concept. It has two 'sides': one that is used by the user to send/receive data (the `stream` // object itself) and one that is used by a Connection to read/write frames to/from the other peer // (`stream.upstream`). @@ -40,7 +40,7 @@ exports.Stream = Stream; // that are to be sent/arrived to/from the peer and are related to this stream. // // Headers are always in the [regular node.js header format][1]. -// [1]: http://nodejs.org/api/http.html#http_message_headers +// [1]: https://nodejs.org/api/http.html#http_message_headers // Constructor // ----------- @@ -182,7 +182,7 @@ Stream.prototype.altsvc = function altsvc(host, port, protocolID, maxAge, origin // [Flow](flow.html). The [Connection](connection.html) object instantiating the stream will read // and write frames to/from it. The stream itself is a regular [Duplex stream][1], and is used by // the user to write or read the body of the request. -// [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex +// [1]: https://nodejs.org/api/stream.html#stream_class_stream_duplex // upstream side stream user side // @@ -352,7 +352,7 @@ Stream.prototype._finishing = function _finishing() { } }; -// [Stream States](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.1) +// [Stream States](https://tools.ietf.org/html/rfc7540#section-5.1) // ---------------- // // +--------+ diff --git a/package.json b/package.json index e774c7e..1e73077 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "scripts": { "test": "istanbul test _mocha -- --reporter spec --slow 500 --timeout 15000", - "doc": "docco lib/* --output doc --layout parallel --css doc/docco.css" + "doc": "docco lib/* --output doc --layout parallel --template root.jst --css doc/docco.css && docco lib/protocol/* --output doc/protocol --layout parallel --template protocol.jst --css doc/docco.css" }, "repository": { "type": "git", From ba87b529ddb2ccc8bc308fec83941b9e823f7820 Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Tue, 6 Sep 2016 14:18:10 -0700 Subject: [PATCH 36/41] Fix splitting for large frames. Fixes #207 I'm honestly not sure why this EVER worked - we were never limiting DATA frames to anything less than the flow control window. This fixes that to take into account the (default) max allowed payload size. Tested and works fine for me with node 6.4.0 --- example/server.js | 11 +++++++++++ lib/protocol/flow.js | 7 ++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/example/server.js b/example/server.js index 455e259..66d8f89 100644 --- a/example/server.js +++ b/example/server.js @@ -30,6 +30,17 @@ function onRequest(request, response) { fileStream.on('finish',response.end); } + // Example for testing large (boundary-sized) frames. + else if (request.url === "/largeframe") { + response.writeHead(200); + var body = 'a'; + for (var i = 0; i < 14; i++) { + body += body; + } + body = body + 'a'; + response.end(body); + } + // Otherwise responding with 404. else { response.writeHead(404); diff --git a/lib/protocol/flow.js b/lib/protocol/flow.js index 1647807..75562f0 100644 --- a/lib/protocol/flow.js +++ b/lib/protocol/flow.js @@ -249,8 +249,9 @@ Flow.prototype._parentPush = function _parentPush(frame) { // did not push the whole frame to the output queue (but maybe it did push part of the frame). Flow.prototype._push = function _push(frame) { var data = frame && (frame.type === 'DATA') && frame.data; + var maxFrameLength = (this._window < 16384) ? this._window : 16384; - if (!data || (data.length <= this._window)) { + if (!data || (data.length <= maxFrameLength)) { return this._parentPush(frame); } @@ -261,12 +262,12 @@ Flow.prototype._push = function _push(frame) { else { this._log.trace({ frame: frame, size: frame.data.length, forwardable: this._window }, 'Splitting out forwardable part of a DATA frame.'); - frame.data = data.slice(this._window); + frame.data = data.slice(maxFrameLength); this._parentPush({ type: 'DATA', flags: {}, stream: frame.stream, - data: data.slice(0, this._window) + data: data.slice(0, maxFrameLength) }); return null; } From 8e4b02c25a0045d2cb380c252ac95bc304b7eff1 Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Tue, 6 Sep 2016 14:27:19 -0700 Subject: [PATCH 37/41] Release v3.3.5 --- HISTORY.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 37dfed4..35e2ec8 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,9 @@ Version history =============== +### 3.3.5 (2016-09-06) ### +* Fix issues with large DATA frames (https://github.com/molnarg/node-http2/issues/207) + ### 3.3.4 (2016-04-22) ### * More PR bugfixes (https://github.com/molnarg/node-http2/issues?q=milestone%3Av3.3.4) diff --git a/package.json b/package.json index 1e73077..9c8f591 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "http2", - "version": "3.3.4", + "version": "3.3.5", "description": "An HTTP/2 client and server implementation", "main": "lib/index.js", "engines" : { From 8c83102b3f2ab8007550dcf97b128ba251404782 Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Tue, 6 Sep 2016 21:27:25 -0700 Subject: [PATCH 38/41] Fix flow control tests --- lib/protocol/flow.js | 2 +- test/flow.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/protocol/flow.js b/lib/protocol/flow.js index 75562f0..4ec5649 100644 --- a/lib/protocol/flow.js +++ b/lib/protocol/flow.js @@ -167,7 +167,7 @@ Flow.prototype._read = function _read() { } while (moreNeeded && (this._queue.length > 0)); this._readableState.sync = false; - assert((moreNeeded == false) || // * output queue is full + assert((!moreNeeded) || // * output queue is full (this._queue.length === 0) || // * flow control queue is empty (!this._window && (this._queue[0].type === 'DATA'))); // * waiting for window update } diff --git a/test/flow.js b/test/flow.js index a77507a..a077c68 100644 --- a/test/flow.js +++ b/test/flow.js @@ -3,7 +3,7 @@ var util = require('./util'); var Flow = require('../lib/protocol/flow').Flow; -var MAX_PAYLOAD_SIZE = 4096; +var MAX_PAYLOAD_SIZE = 16384; function createFlow(log) { var flowControlId = util.random(10, 100); From b7071e1c40aa87b680227add0a61dfa0c6cf818f Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Tue, 6 Sep 2016 20:30:51 -0700 Subject: [PATCH 39/41] Update gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index da9d5e5..bc48362 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ node_modules .idea coverage doc -.vscode +.vscode/.browse* +npm-debug.log +typings \ No newline at end of file From 0ae85eb4e90c990f1047d26dde1fa1305a780c4e Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Thu, 15 Sep 2016 15:04:49 -0700 Subject: [PATCH 40/41] Properly handle SETTINGS_HEADER_TABLE_SIZE --- lib/protocol/compressor.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/protocol/compressor.js b/lib/protocol/compressor.js index f1d1795..3923a91 100644 --- a/lib/protocol/compressor.js +++ b/lib/protocol/compressor.js @@ -1090,11 +1090,20 @@ function Compressor(log, type) { assert((type === 'REQUEST') || (type === 'RESPONSE')); this._table = new HeaderTable(this._log); + + this.tableSizeChangePending = false; + this.lowestTableSizePending = 0; + this.tableSizeSetting = DEFAULT_HEADER_TABLE_LIMIT; } // Changing the header table size Compressor.prototype.setTableSizeLimit = function setTableSizeLimit(size) { this._table.setSizeLimit(size); + if (!this.tableSizeChangePending || size < this.lowestTableSizePending) { + this.lowestTableSizePending = size; + } + this.tableSizeSetting = size; + this.tableSizeChangePending = true; }; // `compress` takes a header set, and compresses it using a new `HeaderSetCompressor` stream @@ -1102,6 +1111,16 @@ Compressor.prototype.setTableSizeLimit = function setTableSizeLimit(size) { // but the API becomes simpler. Compressor.prototype.compress = function compress(headers) { var compressor = new HeaderSetCompressor(this._log, this._table); + + if (this.tableSizeChangePending) { + if (this.lowestTableSizePending < this.tableSizeSetting) { + compressor.send({contextUpdate: true, newMaxSize: this.lowestTableSizePending, + name: "", value: "", index: 0}); + } + compressor.send({contextUpdate: true, newMaxSize: this.tableSizeSetting, + name: "", value: "", index: 0}); + this.tableSizeChangePending = false; + } var colonHeaders = []; var nonColonHeaders = []; From 2c4c310f414d3249421340c0e2618873b07c17cb Mon Sep 17 00:00:00 2001 From: Nicholas Hurley Date: Fri, 16 Sep 2016 08:46:09 -0700 Subject: [PATCH 41/41] Release v3.3.6 --- HISTORY.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 35e2ec8..20bd0c3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,9 @@ Version history =============== +### 3.3.6 (2016-09-16) ### +* We were not appropriately sending HPACK context updates when receiving SETTINGS_HEADER_TABLE_SIZE. This release fixes that bug. + ### 3.3.5 (2016-09-06) ### * Fix issues with large DATA frames (https://github.com/molnarg/node-http2/issues/207) diff --git a/package.json b/package.json index 9c8f591..5372f17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "http2", - "version": "3.3.5", + "version": "3.3.6", "description": "An HTTP/2 client and server implementation", "main": "lib/index.js", "engines" : {