diff --git a/lib/internal/http2.js b/lib/internal/http2.js index 0d973ad1c0..0b6fa471c5 100644 --- a/lib/internal/http2.js +++ b/lib/internal/http2.js @@ -312,9 +312,7 @@ class Http2Stream extends Duplex { if (this._handle) { this._handle.setLocalWindowSize(size); } else { - this.once('handle', () => { - this._handle.setLocalWindowSize(size); - }); + this.once('handle', this.setLocalWindowSize.bind(this, size)); } } @@ -322,9 +320,7 @@ class Http2Stream extends Duplex { if (this._handle) { this._handle.changeStreamPriority(parentId, priority, exclusive); } else { - this.once('handle', () => { - this._handle.changeStreamPriority(parentId, priority, exclusive); - }); + this.once('handle', this.changeStreamPriority.bind(this, parentId, priority, exclusive)); } } @@ -332,9 +328,7 @@ class Http2Stream extends Duplex { if (this._handle) { this._handle.respond(); } else { - this.once('handle', () => { - this._handle.respond(); - }); + this.once('handle', onHandleRespond); } } @@ -342,9 +336,7 @@ class Http2Stream extends Duplex { if (this._handle) { this._handle.resume(); } else { - this.once('handle', () => { - this._handle.resume(); - }); + this.once('handle', onHandleResume); } } @@ -352,9 +344,7 @@ class Http2Stream extends Duplex { if (this._handle) { this._handle.sendContinue(); } else { - this.once('handle', () => { - this._handle.sendContinue(); - }); + this.once('handle', this.sendContinue.bind(this)); } } @@ -362,9 +352,7 @@ class Http2Stream extends Duplex { if (this._handle) { this._handle.sendPriority(parentId, priority, exclusive); } else { - this.once('handle', () => { - this._handle.sendPriority(parentId, priority, exclusive); - }); + this.once('handle', this.sendPriority.bind(this, parentId, priority, exclusive)); } } @@ -372,9 +360,7 @@ class Http2Stream extends Duplex { if (this._handle) { this._handle.sendRstStream(code); } else { - this.once('handle', () => { - this._handle.sendRstStream(code); - }); + this.once('handle', this.sendRstStream.bind(this, code)); } } @@ -382,9 +368,7 @@ class Http2Stream extends Duplex { if (this._handle) { return this._handle.sendPushPromise(mapToHeaders(headers)); } else { - this.once('handle', () => { - this._handle.sendPushPromise(mapToHeaders(headers)); - }); + this.once('handle', this.sendPushPromise.bind(this, headers)); } } @@ -392,9 +376,7 @@ class Http2Stream extends Duplex { if (this._handle) { this._handle.addHeader(name, value, noindex); } else { - this.once('handle', () => { - this._handle.addHeader(name, value, noindex); - }); + this.once('handle', this.addHeader.bind(this, name, value, noindex)); } } @@ -402,9 +384,7 @@ class Http2Stream extends Duplex { if (this._handle) { this._handle.addTrailer(name, value, noindex); } else { - this.once('handle', () => { - this._handle.addTrailer(name, value, noindex); - }); + this.once('handle', this.addTrailer.bind(this, name, value, noindex)); } } @@ -464,9 +444,7 @@ class Http2Stream extends Duplex { if (this._handle) { this._handle.finishedWriting(); } else { - this.on('handle', () => { - this._handle.finishedWriting(); - }); + this.on('handle', onHandleFinishedWriting); } } @@ -474,19 +452,33 @@ class Http2Stream extends Duplex { if (this._handle) { this._handle.readStart(); } else { - this.once('handle', () => { - this._handle.readStart(); - }); + this.once('handle', onHandleReadStart); } } } +function onHandleReadStart() { + this._handle.readStart(); +} + +function onHandleFinishedWriting() { + this._handle.finishedWriting(); +} + function onHandleWrite(data, encoding, cb) { return function onWriteFinished() { this._write(data, encoding, cb); }; } +function onHandleRespond() { + this._handle.respond(); +} + +function onHandleResume() { + this._handle.resume(); +} + class Http2Session extends EventEmitter { constructor(type, options, socket) { super(); @@ -495,6 +487,7 @@ class Http2Session extends EventEmitter { this[kHandle] = sessions.alloc(); this[kHandle][kOwner] = this; this[kHandle].reinitialize(type, options, socket._handle._externalStream); + this[kSocket] = socket; } reset() { @@ -635,8 +628,8 @@ class Http2Incoming extends Readable { super(initHttp2IncomingOptions(options)); if (!(stream instanceof Http2Stream)) throw new TypeError('stream argument must be an Http2Stream instance'); - if (!(headers instanceof Map)) - throw new TypeError('headers argument must be a Map'); + if (typeof headers !== 'object') + throw new TypeError('headers argument must be an object'); this[kStream] = stream; this[kHeaders] = headers; this[kFinished] = false; @@ -686,19 +679,19 @@ class Http2ServerRequest extends Http2Incoming { } get method() { - return this.headers.get(constants.HTTP2_HEADER_METHOD); + return this.headers[constants.HTTP2_HEADER_METHOD]; } get authority() { - return this.headers.get(constants.HTTP2_HEADER_AUTHORITY); + return this.headers[constants.HTTP2_HEADER_AUTHORITY]; } get scheme() { - return this.headers.get(constants.HTTP2_HEADER_SCHEME); + return this.headers[constants.HTTP2_HEADER_SCHEME]; } get url() { - return this.headers.get(constants.HTTP2_HEADER_PATH); + return this.headers[constants.HTTP2_HEADER_PATH]; } } @@ -769,18 +762,20 @@ class Http2Outgoing extends Writable { } addHeaders(headers) { - if (!headers) return; - const keys = Object.keys(headers); - for (const key of keys) - this.setHeader(key, headers[key]); + var keys; + if (headers) { + keys = Object.keys(headers); + for (var i = 0; i < keys.length; i++) + this.setHeader(keys[i], headers[keys[i]]); + } return this; } addTrailers(headers) { if (!headers) return; const keys = Object.keys(headers); - for (const key of keys) - this.setTrailer(key, headers[key]); + for (var i = 0; i < keys.length; i++) + this.setTrailer(keys[i], headers[keys[i]]); return this; } @@ -927,44 +922,44 @@ class Http2PushResponse extends EventEmitter { constructor(response) { super(); this[kResponse] = response; - this[kHeaders] = new Map(); - this.headers.set(constants.HTTP2_HEADER_METHOD, 'GET'); - this.headers.set(constants.HTTP2_HEADER_AUTHORITY, - response.stream[kRequest].authority); - this.headers.set(constants.HTTP2_HEADER_SCHEME, - response.stream[kRequest].scheme); + this[kHeaders] = Object.create(null); + this.headers[constants.HTTP2_HEADER_METHOD] = 'GET'; + this.headers[constants.HTTP2_HEADER_AUTHORITY] = + response.stream[kRequest].authority; + this.headers[constants.HTTP2_HEADER_SCHEME] = + response.stream[kRequest].scheme; } get path() { - return this.headers.get(constants.HTTP2_HEADER_PATH); + return this.headers[constants.HTTP2_HEADER_PATH]; } set path(val) { - this.headers.set(constants.HTTP2_HEADER_PATH, String(val)); + this.headers[constants.HTTP2_HEADER_PATH] = String(val); } get method() { - return this.headers.get(constants.HTTP2_HEADER_METHOD); + return this.headers[constants.HTTP2_HEADER_METHOD]; } set method(val) { - this.headers.set(constants.HTTP2_HEADER_METHOD, String(val)); + this.headers[constants.HTTP2_HEADER_METHOD] = String(val); } get authority() { - return this.headers.get(constants.HTTP2_HEADER_AUTHORITY); + return this.headers[constants.HTTP2_HEADER_AUTHORITY]; } set authority(val) { - this.headers.set(constants.HTTP2_HEADER_AUTHORITY, String(val)); + this.headers[constants.HTTP2_HEADER_AUTHORITY] = String(val); } get scheme() { - return this.headers.get(constants.HTTP2_HEADER_SCHEME); + return this.headers[constants.HTTP2_HEADER_SCHEME]; } set scheme(val) { - this.headers.set(constants.HTTP2_HEADER_SCHEME, String(val)); + this.headers[constants.HTTP2_HEADER_SCHEME] = String(val); } get headers() { @@ -1021,7 +1016,7 @@ function isIllegalConnectionSpecificHeader(name, value) { } } -// Converts a ES6 map into an http2.Http2Headers object. +// Converts an object into an http2.Http2Headers object. // The Http2Headers object maintains an internal array // of nghttp2_nv objects that contain a copy of the // header value pairs as an std::vector. To avoid @@ -1029,21 +1024,22 @@ function isIllegalConnectionSpecificHeader(name, value) { // the number of expected items up front (it's less // expensive to count than it is to reallocate). function mapToHeaders(map) { - var size = map.size; - for (const v of map) { - if (Array.isArray(v[1])) { - size += v[1].length - 1; + const keys = Object.keys(map) + var size = keys.length; + for (var i = 0; i < keys.length; i++) { + if (Array.isArray(keys[i])) { + size += keys[i].length - 1; } } const ret = new http2.Http2Headers(size); - if (!(map instanceof Map)) - return ret; - for (const v of map) { - const key = String(v[0]); - const value = v[1]; + + for (i = 0; i < keys.length; i++) { + const key = keys[i] + const value = map[key]; if (Array.isArray(value) && value.length > 0) { - for (const item of value) - ret.add(key, String(item)); + for (var k = 0; k < value.length; k++) { + ret.add(key, String(value[k])); + } } else { ret.add(key, String(value)); } @@ -1103,28 +1099,29 @@ function sessionOnStreamClose(stream, code) { response[kStream] = undefined; stream[kRequest] = undefined; stream[kResponse] = undefined; - setImmediate(() => maybeDestroyStream(stream)); + setImmediate(maybeDestroyStream, stream); } -function sessionOnError(server, socket) { - function fn(error) { - if (server.listenerCount('sessionError') > 0) { - server.emit('sessionError', error); - return; - } - socket.destroy(error); +function sessionOnError() { + const session = this; + const server = session[kServer]; + const socket = session[kSocket]; + + if (server.listenerCount('sessionError') > 0) { + server.emit('sessionError', error); + return; } - return fn; + socket.destroy(error); } -function socketOnTimeout(server, session) { - function fn() { - if (!server.emit('timeout', this)) { - // Session timed out, attempt a graceful exit - session.gracefulTerminate(() => this.destroy()); - } +function socketOnTimeout() { + const socket = this; + const server = socket[kServer]; + + if (!server.emit('timeout', this)) { + // Session timed out, attempt a graceful exit + session.gracefulTerminate(this.destroy.bind(this)); } - return fn; } function socketOnceError(error) { @@ -1171,12 +1168,12 @@ function sessionOnHeaderComplete(stream, flags, headers, category) { if (finished) request[kFinished] = true; - if (headers.has('expect')) { + if (headers.expect) { // If there is an expect header that contains 100-continue, // and the server has a listener for the checkContinue event, // emit the checkContinue event instead of the request event. // This behavior matches the current http/1 API. - if (/^100-continue$/i.test(headers.get('expect'))) { + if (/^100-continue$/i.test(headers.expect)) { if (server.listenerCount('checkContinue') > 0) { request[kInFlight] = true; server.emit('checkContinue', request, response); @@ -1248,7 +1245,7 @@ function connectionListener(socket) { session[kServer] = this; socket[kServer] = this; - session.on('error', sessionOnError(this, socket)); + session.on('error', sessionOnError); // Disable TLS Negotiation on this socket. The HTTP/2 allows renegotiation to // happen up until the initial HTTP/2 session bootstrap. After that, it is @@ -1259,7 +1256,7 @@ function connectionListener(socket) { // Set up the timeout listener if (this.timeout) socket.setTimeout(this.timeout); - socket.on('timeout', socketOnTimeout(this, session)); + socket.on('timeout', socketOnTimeout); // Destroy the session if the socket is destroyed const destroySocket = socket.destroy; @@ -1307,6 +1304,11 @@ function initializeTLSOptions(options) { return options; } +function onErrorSecureServerSession(err, conn) { + if (!this.emit('clientError', err, conn)) + conn.destroy(err); +} + class Http2SecureServerSession extends TLSServer { constructor(options, requestListener) { super(initializeTLSOptions(options), connectionListener); @@ -1314,10 +1316,7 @@ class Http2SecureServerSession extends TLSServer { this.timeout = kDefaultSocketTimeout; if (typeof requestListener === 'function') this.on('request', requestListener); - this.on('tlsClientError', (err, conn) => { - if (!this.emit('clientError', err, conn)) - conn.destroy(err); - }); + this.on('tlsClientError', onErrorSecureServerSession); } setTimeout(msecs, callback) { @@ -1440,7 +1439,7 @@ function clientSessionOnStreamClose(stream, code) { response[kStream] = undefined; stream[kRequest] = undefined; stream[kResponse] = undefined; - setImmediate(() => maybeDestroyStream(stream)); + setImmediate(maybeDestroyStream, stream); } function initializeClientOptions(options) { @@ -1475,9 +1474,8 @@ class Http2ClientSession extends EventEmitter { this[kSocket] = socket; const session = this[kSession] = createClientSession(options, socket); - socket.once('error', (error) => { - console.log(error); - }); + // TODO remove this + socket.once('error', console.log); socket.on('resume', socketOnResume); socket.on('pause', socketOnPause); socket.on('drain', socketOnDrain); @@ -1557,13 +1555,12 @@ class Http2ClientRequest extends Http2Outgoing { var authority = options.hostname; if (options.port) authority += `:${options.port}`; - const headers = this[kHeaders] = new Map(); + const headers = this[kHeaders] = Object.create(null); - headers.set(constants.HTTP2_HEADER_SCHEME, - options.protocol.slice(0, options.protocol.length - 1)); - headers.set(constants.HTTP2_HEADER_METHOD, options.method); - headers.set(constants.HTTP2_HEADER_AUTHORITY, authority); - headers.set(constants.HTTP2_HEADER_PATH, options.pathname); + headers[constants.HTTP2_HEADER_SCHEME] = options.protocol.slice(0, options.protocol.length - 1); + headers[constants.HTTP2_HEADER_METHOD] = options.method; + headers[constants.HTTP2_HEADER_AUTHORITY] = authority; + headers[constants.HTTP2_HEADER_PATH] = options.pathname; if (typeof callback === 'function') this.once('response', callback); @@ -1571,31 +1568,31 @@ class Http2ClientRequest extends Http2Outgoing { setHeader(name, value) { name = String(name).toLowerCase().trim(); - if (this[kHeaders].has(name)) { - const existing = this[kHeaders].get(name); + const existing = this[kHeaders][name]; + if (existing) { if (Array.isArray(existing)) { existing.push(String(value)); } else { - this[kHeaders].set(name, [existing, value]); + this[kHeaders][name] = [existing, value]; } } else { - this[kHeaders].set(name, value); + this[kHeaders][name] = value; } } setTrailer(name, value) { if (!this[kTrailers]) - this[kTrailers] = new Map(); + this[kTrailers] = Object.create(null); name = String(name).toLowerCase().trim(); - if (this[kTrailers].has(name)) { - const existing = this[kTrailers].get(name); + const existing = this[kTrailers][name]; + if (existing) { if (Array.isArray(existing)) { existing.push(String(value)); } else { - this[kTrailers].set(name, [existing, value]); + this[kTrailers][name] = [existing, value]; } } else { - this[kTrailers].set(name, value); + this[kTrailers][name] = value; } } @@ -1605,20 +1602,7 @@ class Http2ClientRequest extends Http2Outgoing { const _handle = this.stream.session.request(mapToHeaders(this[kHeaders]), true); if (_handle instanceof http2.Http2Stream) { this[kId] = _handle.getId(); - this.stream.once('handle', () => { - if (this[kTrailers] instanceof Map) { - for (const v of this[kTrailers]) { - const key = String(v[0]); - const value = v[1]; - if (Array.isArray(value) && value.length > 0) { - for (const item of value) - this.stream.addTrailer(key, String(item)); - } else { - this.stream.addTrailer(key, String(value)); - } - } - } - }); + this.stream.once('handle', addTrailers) this.stream._handle = _handle; } } @@ -1629,13 +1613,29 @@ class Http2ClientRequest extends Http2Outgoing { } } +function addTrailers () { + const request = this[kRequest]; + if (request[kTrailers]) { + // key is coerced on a string on set + for (var key in request[kTrailers]) { + const value = request[kTrailers][key]; + if (Array.isArray(value) && value.length > 0) { + for (var i = 0; i < value.length; i++) + this.addTrailer(key, String(value[i])); + } else { + this.addTrailer(key, String(value)); + } + } + } +} + class Http2ClientResponse extends Http2Incoming { constructor(stream, headers, options) { super(stream, headers, options); } get status() { - return this.headers.get(constants.HTTP2_HEADER_STATUS) | 0; + return this.headers[constants.HTTP2_HEADER_STATUS] | 0; } }