diff options
author | Anatoli Papirovski <apapirovski@mac.com> | 2017-10-02 21:56:53 -0400 |
---|---|---|
committer | Matteo Collina <hello@matteocollina.com> | 2017-10-06 14:04:22 -0700 |
commit | 2da7d9b8207d9c35d199734da75fa2bb15f326b6 (patch) | |
tree | 9aa1ed51d29bc3d9241539e9ded4d0e4da951970 /test | |
parent | 4f339b54e9cd8a2cb69b41d87832ad8ca3a6b5e2 (diff) | |
download | android-node-v8-2da7d9b8207d9c35d199734da75fa2bb15f326b6.tar.gz android-node-v8-2da7d9b8207d9c35d199734da75fa2bb15f326b6.tar.bz2 android-node-v8-2da7d9b8207d9c35d199734da75fa2bb15f326b6.zip |
http2: near full http1 compatibility, add tests
Extensive re-work of http1 compatibility layer based on tests in
express, on-finished and finalhandler. Fix handling of HEAD
method to match http1. Adjust write, end, etc. to call writeHead
as in http1 and as expected by user-land modules. Add socket
proxy that instead uses the Http2Stream for the vast majority of
socket interactions. Add and change tests to closer represent
http1 behaviour.
Refs: https://github.com/nodejs/node/pull/15633
Refs: https://github.com/expressjs/express/tree/master/test
Refs: https://github.com/jshttp/on-finished/blob/master/test/test.js
Refs: https://github.com/pillarjs/finalhandler/blob/master/test/test.js
PR-URL: https://github.com/nodejs/node/pull/15702
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Diffstat (limited to 'test')
16 files changed, 552 insertions, 33 deletions
diff --git a/test/parallel/test-http2-compat-serverrequest-end.js b/test/parallel/test-http2-compat-serverrequest-end.js index 60a4876482..b6bfd04089 100644 --- a/test/parallel/test-http2-compat-serverrequest-end.js +++ b/test/parallel/test-http2-compat-serverrequest-end.js @@ -3,6 +3,7 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const assert = require('assert'); const h2 = require('http2'); // Http2ServerRequest should always end readable stream @@ -12,9 +13,17 @@ const server = h2.createServer(); server.listen(0, common.mustCall(function() { const port = server.address().port; server.once('request', common.mustCall(function(request, response) { + assert.strictEqual(request.complete, false); request.on('data', () => {}); request.on('end', common.mustCall(() => { + assert.strictEqual(request.complete, true); response.on('finish', common.mustCall(function() { + // the following tests edge cases on request socket + // right after finished fires but before backing + // Http2Stream is destroyed + assert.strictEqual(request.socket.readable, request.stream.readable); + assert.strictEqual(request.socket.readable, false); + server.close(); })); response.end(); diff --git a/test/parallel/test-http2-compat-serverrequest-headers.js b/test/parallel/test-http2-compat-serverrequest-headers.js index 05e645a362..58cc52c64f 100644 --- a/test/parallel/test-http2-compat-serverrequest-headers.js +++ b/test/parallel/test-http2-compat-serverrequest-headers.js @@ -41,6 +41,27 @@ server.listen(0, common.mustCall(function() { request.url = '/one'; assert.strictEqual(request.url, '/one'); + // third-party plugins for packages like express use query params to + // change the request method + request.method = 'POST'; + assert.strictEqual(request.method, 'POST'); + common.expectsError( + () => request.method = ' ', + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "method" argument must be of type string' + } + ); + common.expectsError( + () => request.method = true, + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "method" argument must be of type string' + } + ); + response.on('finish', common.mustCall(function() { server.close(); })); diff --git a/test/parallel/test-http2-compat-serverrequest-pipe.js b/test/parallel/test-http2-compat-serverrequest-pipe.js index a8ef688fd7..24cee1c460 100644 --- a/test/parallel/test-http2-compat-serverrequest-pipe.js +++ b/test/parallel/test-http2-compat-serverrequest-pipe.js @@ -19,6 +19,7 @@ const server = http2.createServer(); server.on('request', common.mustCall((req, res) => { const dest = req.pipe(fs.createWriteStream(fn)); dest.on('finish', common.mustCall(() => { + assert.strictEqual(req.complete, true); assert.deepStrictEqual(fs.readFileSync(loc), fs.readFileSync(fn)); fs.unlinkSync(fn); res.end(); diff --git a/test/parallel/test-http2-compat-serverrequest.js b/test/parallel/test-http2-compat-serverrequest.js index 31cc5ff4ef..edcd7a8f8c 100644 --- a/test/parallel/test-http2-compat-serverrequest.js +++ b/test/parallel/test-http2-compat-serverrequest.js @@ -19,9 +19,6 @@ server.listen(0, common.mustCall(function() { httpVersionMinor: 0 }; - assert.strictEqual(request.closed, false); - assert.strictEqual(request.code, h2.constants.NGHTTP2_NO_ERROR); - assert.strictEqual(request.httpVersion, expected.version); assert.strictEqual(request.httpVersionMajor, expected.httpVersionMajor); assert.strictEqual(request.httpVersionMinor, expected.httpVersionMinor); @@ -31,10 +28,8 @@ server.listen(0, common.mustCall(function() { assert.strictEqual(request.socket, request.connection); response.on('finish', common.mustCall(function() { - assert.strictEqual(request.closed, true); - assert.strictEqual(request.code, h2.constants.NGHTTP2_NO_ERROR); process.nextTick(() => { - assert.strictEqual(request.socket, undefined); + assert.ok(request.socket); server.close(); }); })); diff --git a/test/parallel/test-http2-compat-serverresponse-destroy.js b/test/parallel/test-http2-compat-serverresponse-destroy.js index e8f648bf29..77e761b622 100644 --- a/test/parallel/test-http2-compat-serverresponse-destroy.js +++ b/test/parallel/test-http2-compat-serverresponse-destroy.js @@ -22,7 +22,6 @@ const server = http2.createServer(common.mustCall((req, res) => { res.on('finish', common.mustCall(() => { assert.doesNotThrow(() => res.destroy(nextError)); - assert.strictEqual(res.closed, true); process.nextTick(() => { assert.doesNotThrow(() => res.destroy(nextError)); }); diff --git a/test/parallel/test-http2-compat-serverresponse-end.js b/test/parallel/test-http2-compat-serverresponse-end.js index 76cb692938..fafb3ea76d 100644 --- a/test/parallel/test-http2-compat-serverresponse-end.js +++ b/test/parallel/test-http2-compat-serverresponse-end.js @@ -1,6 +1,12 @@ 'use strict'; -const { mustCall, mustNotCall, hasCrypto, skip } = require('../common'); +const { + mustCall, + mustNotCall, + hasCrypto, + platformTimeout, + skip +} = require('../common'); if (!hasCrypto) skip('missing crypto'); const { strictEqual } = require('assert'); @@ -18,15 +24,16 @@ const { // It may be invoked repeatedly without throwing errors // but callback will only be called once const server = createServer(mustCall((request, response) => { - strictEqual(response.closed, false); response.end('end', 'utf8', mustCall(() => { - strictEqual(response.closed, true); response.end(mustNotCall()); process.nextTick(() => { response.end(mustNotCall()); server.close(); }); })); + response.on('finish', mustCall(() => { + response.end(mustNotCall()); + })); response.end(mustNotCall()); })); server.listen(0, mustCall(() => { @@ -111,12 +118,77 @@ const { } { - // Http2ServerResponse.end is not necessary on HEAD requests since the stream - // is already closed. Headers, however, can still be sent to the client. + // Http2ServerResponse.end is necessary on HEAD requests in compat + // for http1 compatibility const server = createServer(mustCall((request, response) => { strictEqual(response.finished, true); response.writeHead(HTTP_STATUS_OK, { foo: 'bar' }); - response.end(mustNotCall()); + response.end('data', mustCall()); + })); + server.listen(0, mustCall(() => { + const { port } = server.address(); + const url = `http://localhost:${port}`; + const client = connect(url, mustCall(() => { + const headers = { + ':path': '/', + ':method': 'HEAD', + ':scheme': 'http', + ':authority': `localhost:${port}` + }; + const request = client.request(headers); + request.on('response', mustCall((headers, flags) => { + strictEqual(headers[HTTP2_HEADER_STATUS], HTTP_STATUS_OK); + strictEqual(flags, 5); // the end of stream flag is set + strictEqual(headers.foo, 'bar'); + })); + request.on('data', mustNotCall()); + request.on('end', mustCall(() => { + client.destroy(); + server.close(); + })); + request.end(); + request.resume(); + })); + })); +} + +{ + // .end should trigger 'end' event on request if user did not attempt + // to read from the request + const server = createServer(mustCall((request, response) => { + request.on('end', mustCall()); + response.end(); + })); + server.listen(0, mustCall(() => { + const { port } = server.address(); + const url = `http://localhost:${port}`; + const client = connect(url, mustCall(() => { + const headers = { + ':path': '/', + ':method': 'HEAD', + ':scheme': 'http', + ':authority': `localhost:${port}` + }; + const request = client.request(headers); + request.on('data', mustNotCall()); + request.on('end', mustCall(() => { + client.destroy(); + server.close(); + })); + request.end(); + request.resume(); + })); + })); +} + + +{ + // Should be able to call .end with cb from stream 'streamClosed' + const server = createServer(mustCall((request, response) => { + response.writeHead(HTTP_STATUS_OK, { foo: 'bar' }); + response.stream.on('streamClosed', mustCall(() => { + response.end(mustCall()); + })); })); server.listen(0, mustCall(() => { const { port } = server.address(); @@ -144,3 +216,110 @@ const { })); })); } + +{ + // Should be able to respond to HEAD request after timeout + const server = createServer(mustCall((request, response) => { + setTimeout(mustCall(() => { + response.writeHead(HTTP_STATUS_OK, { foo: 'bar' }); + response.end('data', mustCall()); + }), platformTimeout(10)); + })); + server.listen(0, mustCall(() => { + const { port } = server.address(); + const url = `http://localhost:${port}`; + const client = connect(url, mustCall(() => { + const headers = { + ':path': '/', + ':method': 'HEAD', + ':scheme': 'http', + ':authority': `localhost:${port}` + }; + const request = client.request(headers); + request.on('response', mustCall((headers, flags) => { + strictEqual(headers[HTTP2_HEADER_STATUS], HTTP_STATUS_OK); + strictEqual(flags, 5); // the end of stream flag is set + strictEqual(headers.foo, 'bar'); + })); + request.on('data', mustNotCall()); + request.on('end', mustCall(() => { + client.destroy(); + server.close(); + })); + request.end(); + request.resume(); + })); + })); +} + +{ + // finish should only trigger after 'end' is called + const server = createServer(mustCall((request, response) => { + let finished = false; + response.writeHead(HTTP_STATUS_OK, { foo: 'bar' }); + response.on('finish', mustCall(() => { + finished = false; + })); + response.end('data', mustCall(() => { + strictEqual(finished, false); + response.end('data', mustNotCall()); + })); + })); + server.listen(0, mustCall(() => { + const { port } = server.address(); + const url = `http://localhost:${port}`; + const client = connect(url, mustCall(() => { + const headers = { + ':path': '/', + ':method': 'HEAD', + ':scheme': 'http', + ':authority': `localhost:${port}` + }; + const request = client.request(headers); + request.on('response', mustCall((headers, flags) => { + strictEqual(headers[HTTP2_HEADER_STATUS], HTTP_STATUS_OK); + strictEqual(flags, 5); // the end of stream flag is set + strictEqual(headers.foo, 'bar'); + })); + request.on('data', mustNotCall()); + request.on('end', mustCall(() => { + client.destroy(); + server.close(); + })); + request.end(); + request.resume(); + })); + })); +} + +{ + // Should be able to respond to HEAD with just .end + const server = createServer(mustCall((request, response) => { + response.end('data', mustCall()); + response.end(mustNotCall()); + })); + server.listen(0, mustCall(() => { + const { port } = server.address(); + const url = `http://localhost:${port}`; + const client = connect(url, mustCall(() => { + const headers = { + ':path': '/', + ':method': 'HEAD', + ':scheme': 'http', + ':authority': `localhost:${port}` + }; + const request = client.request(headers); + request.on('response', mustCall((headers, flags) => { + strictEqual(headers[HTTP2_HEADER_STATUS], HTTP_STATUS_OK); + strictEqual(flags, 5); // the end of stream flag is set + })); + request.on('data', mustNotCall()); + request.on('end', mustCall(() => { + client.destroy(); + server.close(); + })); + request.end(); + request.resume(); + })); + })); +} diff --git a/test/parallel/test-http2-compat-serverresponse-finished.js b/test/parallel/test-http2-compat-serverresponse-finished.js index cd17ff492b..b816b92220 100644 --- a/test/parallel/test-http2-compat-serverresponse-finished.js +++ b/test/parallel/test-http2-compat-serverresponse-finished.js @@ -5,19 +5,23 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); const h2 = require('http2'); +const net = require('net'); // Http2ServerResponse.finished const server = h2.createServer(); server.listen(0, common.mustCall(function() { const port = server.address().port; server.once('request', common.mustCall(function(request, response) { + assert.ok(response.socket instanceof net.Socket); + assert.ok(response.connection instanceof net.Socket); + assert.strictEqual(response.socket, response.connection); + response.on('finish', common.mustCall(function() { - assert.ok(request.stream !== undefined); - assert.ok(response.stream !== undefined); - server.close(); + assert.strictEqual(response.socket, undefined); + assert.strictEqual(response.connection, undefined); process.nextTick(common.mustCall(() => { - assert.strictEqual(request.stream, undefined); - assert.strictEqual(response.stream, undefined); + assert.ok(response.stream); + server.close(); })); })); assert.strictEqual(response.finished, false); diff --git a/test/parallel/test-http2-compat-serverresponse-flushheaders.js b/test/parallel/test-http2-compat-serverresponse-flushheaders.js index 48bf4dd2d4..68d4789f69 100644 --- a/test/parallel/test-http2-compat-serverresponse-flushheaders.js +++ b/test/parallel/test-http2-compat-serverresponse-flushheaders.js @@ -15,18 +15,23 @@ server.listen(0, common.mustCall(function() { const port = server.address().port; server.once('request', common.mustCall(function(request, response) { assert.strictEqual(response.headersSent, false); + assert.strictEqual(response._header, false); // alias for headersSent response.flushHeaders(); assert.strictEqual(response.headersSent, true); + assert.strictEqual(response._header, true); response.flushHeaders(); // Idempotent common.expectsError(() => { response.writeHead(400, { 'foo-bar': 'abc123' }); }, { - code: 'ERR_HTTP2_INFO_HEADERS_AFTER_RESPOND' + code: 'ERR_HTTP2_HEADERS_SENT' }); response.on('finish', common.mustCall(function() { server.close(); + process.nextTick(() => { + response.flushHeaders(); // Idempotent + }); })); serverResponse = response; })); diff --git a/test/parallel/test-http2-compat-serverresponse-headers-after-destroy.js b/test/parallel/test-http2-compat-serverresponse-headers-after-destroy.js new file mode 100644 index 0000000000..fb1e369f78 --- /dev/null +++ b/test/parallel/test-http2-compat-serverresponse-headers-after-destroy.js @@ -0,0 +1,47 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const assert = require('assert'); +const h2 = require('http2'); + +// makes sure that Http2ServerResponse setHeader & removeHeader, do not throw +// any errors if the stream was destroyed before headers were sent + +const server = h2.createServer(); +server.listen(0, common.mustCall(function() { + const port = server.address().port; + server.once('request', common.mustCall(function(request, response) { + response.destroy(); + + response.on('finish', common.mustCall(() => { + assert.strictEqual(response.headersSent, false); + assert.doesNotThrow(() => response.setHeader('test', 'value')); + assert.doesNotThrow(() => response.removeHeader('test', 'value')); + + process.nextTick(() => { + assert.doesNotThrow(() => response.setHeader('test', 'value')); + assert.doesNotThrow(() => response.removeHeader('test', 'value')); + + server.close(); + }); + })); + })); + + const url = `http://localhost:${port}`; + const client = h2.connect(url, common.mustCall(function() { + const headers = { + ':path': '/', + ':method': 'GET', + ':scheme': 'http', + ':authority': `localhost:${port}` + }; + const request = client.request(headers); + request.on('end', common.mustCall(function() { + client.destroy(); + })); + request.end(); + request.resume(); + })); +})); diff --git a/test/parallel/test-http2-compat-serverresponse-headers.js b/test/parallel/test-http2-compat-serverresponse-headers.js index 25ec4fc11b..aae1d96c55 100644 --- a/test/parallel/test-http2-compat-serverresponse-headers.js +++ b/test/parallel/test-http2-compat-serverresponse-headers.js @@ -124,14 +124,44 @@ server.listen(0, common.mustCall(function() { response.sendDate = false; assert.strictEqual(response.sendDate, false); - assert.strictEqual(response.code, h2.constants.NGHTTP2_NO_ERROR); - response.on('finish', common.mustCall(function() { - assert.strictEqual(response.code, h2.constants.NGHTTP2_NO_ERROR); assert.strictEqual(response.headersSent, true); + + common.expectsError( + () => response.setHeader(real, expectedValue), + { + code: 'ERR_HTTP2_HEADERS_SENT', + type: Error, + message: 'Response has already been initiated.' + } + ); + common.expectsError( + () => response.removeHeader(real, expectedValue), + { + code: 'ERR_HTTP2_HEADERS_SENT', + type: Error, + message: 'Response has already been initiated.' + } + ); + process.nextTick(() => { - // can access headersSent after stream is undefined - assert.strictEqual(response.stream, undefined); + common.expectsError( + () => response.setHeader(real, expectedValue), + { + code: 'ERR_HTTP2_HEADERS_SENT', + type: Error, + message: 'Response has already been initiated.' + } + ); + common.expectsError( + () => response.removeHeader(real, expectedValue), + { + code: 'ERR_HTTP2_HEADERS_SENT', + type: Error, + message: 'Response has already been initiated.' + } + ); + assert.strictEqual(response.headersSent, true); server.close(); }); diff --git a/test/parallel/test-http2-compat-serverresponse-writehead.js b/test/parallel/test-http2-compat-serverresponse-writehead.js index f6d74630fd..704f199ca2 100644 --- a/test/parallel/test-http2-compat-serverresponse-writehead.js +++ b/test/parallel/test-http2-compat-serverresponse-writehead.js @@ -16,7 +16,7 @@ server.listen(0, common.mustCall(function() { response.writeHead(418, { 'foo-bar': 'abc123' }); // Override common.expectsError(() => { response.writeHead(300); }, { - code: 'ERR_HTTP2_INFO_HEADERS_AFTER_RESPOND' + code: 'ERR_HTTP2_HEADERS_SENT' }); response.on('finish', common.mustCall(function() { diff --git a/test/parallel/test-http2-compat-socket-set.js b/test/parallel/test-http2-compat-socket-set.js new file mode 100644 index 0000000000..7411fef602 --- /dev/null +++ b/test/parallel/test-http2-compat-socket-set.js @@ -0,0 +1,107 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const assert = require('assert'); +const h2 = require('http2'); + +// Tests behaviour of the proxied socket in Http2ServerRequest +// & Http2ServerResponse - specifically property setters + +const errMsg = { + code: 'ERR_HTTP2_NO_SOCKET_MANIPULATION', + type: Error, + message: 'HTTP/2 sockets should not be directly read from, written to, ' + + 'paused and/or resumed.' +}; + +const server = h2.createServer(); + +server.on('request', common.mustCall(function(request, response) { + const noop = () => {}; + + assert.strictEqual(request.stream.destroyed, false); + request.socket.destroyed = true; + assert.strictEqual(request.stream.destroyed, true); + request.socket.destroyed = false; + + assert.strictEqual(request.stream.readable, false); + request.socket.readable = true; + assert.strictEqual(request.stream.readable, true); + + assert.strictEqual(request.stream.writable, true); + request.socket.writable = false; + assert.strictEqual(request.stream.writable, false); + + const realOn = request.stream.on; + request.socket.on = noop; + assert.strictEqual(request.stream.on, noop); + request.stream.on = realOn; + + const realOnce = request.stream.once; + request.socket.once = noop; + assert.strictEqual(request.stream.once, noop); + request.stream.once = realOnce; + + const realEnd = request.stream.end; + request.socket.end = noop; + assert.strictEqual(request.stream.end, noop); + request.socket.end = common.mustCall(); + request.socket.end(); + request.stream.end = realEnd; + + const realEmit = request.stream.emit; + request.socket.emit = noop; + assert.strictEqual(request.stream.emit, noop); + request.stream.emit = realEmit; + + const realDestroy = request.stream.destroy; + request.socket.destroy = noop; + assert.strictEqual(request.stream.destroy, noop); + request.stream.destroy = realDestroy; + + request.socket.setTimeout = noop; + assert.strictEqual(request.stream.session.setTimeout, noop); + + assert.strictEqual(request.stream.session.socket._isProcessing, undefined); + request.socket._isProcessing = true; + assert.strictEqual(request.stream.session.socket._isProcessing, true); + + common.expectsError(() => request.socket.read = noop, errMsg); + common.expectsError(() => request.socket.write = noop, errMsg); + common.expectsError(() => request.socket.pause = noop, errMsg); + common.expectsError(() => request.socket.resume = noop, errMsg); + + request.stream.on('finish', common.mustCall(() => { + setImmediate(() => { + request.socket.setTimeout = noop; + assert.strictEqual(request.stream.setTimeout, noop); + + assert.strictEqual(request.stream._isProcessing, undefined); + request.socket._isProcessing = true; + assert.strictEqual(request.stream._isProcessing, true); + }); + })); + response.stream.destroy(); +})); + +server.listen(0, common.mustCall(function() { + const port = server.address().port; + const url = `http://localhost:${port}`; + const client = h2.connect(url, common.mustCall(function() { + const headers = { + ':path': '/', + ':method': 'GET', + ':scheme': 'http', + ':authority': `localhost:${port}` + }; + const request = client.request(headers); + request.on('end', common.mustCall(() => { + client.destroy(); + server.close(); + })); + request.end(); + request.resume(); + })); +})); diff --git a/test/parallel/test-http2-compat-socket.js b/test/parallel/test-http2-compat-socket.js new file mode 100644 index 0000000000..8292232f74 --- /dev/null +++ b/test/parallel/test-http2-compat-socket.js @@ -0,0 +1,89 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const assert = require('assert'); +const h2 = require('http2'); +const net = require('net'); + +// Tests behaviour of the proxied socket in Http2ServerRequest +// & Http2ServerResponse - this proxy socket should mimic the +// behaviour of http1 but against the http2 api & model + +const errMsg = { + code: 'ERR_HTTP2_NO_SOCKET_MANIPULATION', + type: Error, + message: 'HTTP/2 sockets should not be directly read from, written to, ' + + 'paused and/or resumed.' +}; + +const server = h2.createServer(); + +server.on('request', common.mustCall(function(request, response) { + assert.ok(request.socket instanceof net.Socket); + assert.ok(response.socket instanceof net.Socket); + assert.strictEqual(request.socket, response.socket); + + assert.ok(request.socket.readable); + request.resume(); + assert.ok(request.socket.writable); + assert.strictEqual(request.socket.destroyed, false); + + request.socket.setTimeout(987); + assert.strictEqual(request.stream.session._idleTimeout, 987); + request.socket.setTimeout(0); + + common.expectsError(() => request.socket.read(), errMsg); + common.expectsError(() => request.socket.write(), errMsg); + common.expectsError(() => request.socket.pause(), errMsg); + common.expectsError(() => request.socket.resume(), errMsg); + + // should have correct this context for socket methods & getters + assert.ok(request.socket.address() != null); + assert.ok(request.socket.remotePort); + + request.on('end', common.mustCall(() => { + assert.strictEqual(request.socket.readable, false); + assert.doesNotThrow(() => response.socket.destroy()); + })); + response.on('finish', common.mustCall(() => { + assert.ok(request.socket); + assert.strictEqual(response.socket, undefined); + assert.ok(request.socket.destroyed); + assert.strictEqual(request.socket.readable, false); + process.nextTick(() => { + assert.strictEqual(request.socket.writable, false); + server.close(); + }); + })); + + // properties that do not exist on the proxy are retrieved from the socket + assert.ok(request.socket._server); + assert.strictEqual(request.socket.connecting, false); + + // socket events are bound and emitted on Http2Stream + request.socket.on('streamClosed', common.mustCall()); + request.socket.once('streamClosed', common.mustCall()); + request.socket.on('testEvent', common.mustCall()); + request.socket.emit('testEvent'); +})); + +server.listen(0, common.mustCall(function() { + const port = server.address().port; + const url = `http://localhost:${port}`; + const client = h2.connect(url, common.mustCall(() => { + const headers = { + ':path': '/', + ':method': 'GET', + ':scheme': 'http', + ':authority': `localhost:${port}` + }; + const request = client.request(headers); + request.on('end', common.mustCall(() => { + client.destroy(); + })); + request.end(); + request.resume(); + })); +})); diff --git a/test/parallel/test-http2-options-max-reserved-streams.js b/test/parallel/test-http2-options-max-reserved-streams.js index 5e61907770..17009a4c11 100644 --- a/test/parallel/test-http2-options-max-reserved-streams.js +++ b/test/parallel/test-http2-options-max-reserved-streams.js @@ -3,6 +3,7 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const assert = require('assert'); const h2 = require('http2'); const server = h2.createServer(); @@ -32,11 +33,9 @@ server.on('stream', common.mustCall((stream) => { }, common.mustCall((pushedStream) => { pushedStream.respond({ ':status': 200 }); pushedStream.on('aborted', common.mustCall()); - pushedStream.on('error', common.mustCall(common.expectsError({ - code: 'ERR_HTTP2_STREAM_ERROR', - type: Error, - message: 'Stream closed with error code 8' - }))); + pushedStream.on('error', common.mustNotCall()); + pushedStream.on('streamClosed', + common.mustCall((code) => assert.strictEqual(code, 8))); })); stream.end('hello world'); diff --git a/test/parallel/test-http2-server-rst-stream.js b/test/parallel/test-http2-server-rst-stream.js index edaaed35c6..b92217dc99 100644 --- a/test/parallel/test-http2-server-rst-stream.js +++ b/test/parallel/test-http2-server-rst-stream.js @@ -17,7 +17,7 @@ const { NGHTTP2_INTERNAL_ERROR } = http2.constants; -const errCheck = common.expectsError({ code: 'ERR_HTTP2_STREAM_ERROR' }, 8); +const errCheck = common.expectsError({ code: 'ERR_HTTP2_STREAM_ERROR' }, 6); function checkRstCode(rstMethod, expectRstCode) { const server = http2.createServer(); @@ -32,8 +32,11 @@ function checkRstCode(rstMethod, expectRstCode) { else stream[rstMethod](); - if (expectRstCode > NGHTTP2_NO_ERROR) { + if (expectRstCode !== NGHTTP2_NO_ERROR && + expectRstCode !== NGHTTP2_CANCEL) { stream.on('error', common.mustCall(errCheck)); + } else { + stream.on('error', common.mustNotCall()); } }); @@ -58,8 +61,11 @@ function checkRstCode(rstMethod, expectRstCode) { req.on('aborted', common.mustCall()); req.on('end', common.mustCall()); - if (expectRstCode > NGHTTP2_NO_ERROR) { + if (expectRstCode !== NGHTTP2_NO_ERROR && + expectRstCode !== NGHTTP2_CANCEL) { req.on('error', common.mustCall(errCheck)); + } else { + req.on('error', common.mustNotCall()); } })); diff --git a/test/parallel/test-http2-stream-destroy-event-order.js b/test/parallel/test-http2-stream-destroy-event-order.js new file mode 100644 index 0000000000..16bb5e6a62 --- /dev/null +++ b/test/parallel/test-http2-stream-destroy-event-order.js @@ -0,0 +1,28 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const assert = require('assert'); +const http2 = require('http2'); + +let client; +let req; +const server = http2.createServer(); +server.on('stream', common.mustCall((stream) => { + stream.on('error', common.mustCall(() => { + stream.on('streamClosed', common.mustCall((code) => { + assert.strictEqual(code, 2); + client.destroy(); + server.close(); + })); + })); + + req.rstStream(2); +})); +server.listen(0, common.mustCall(() => { + client = http2.connect(`http://localhost:${server.address().port}`); + req = client.request(); + req.resume(); + req.on('error', common.mustCall()); +})); |