From 6bfdeedce5529810dbe7c61bd712fc50174a19f1 Mon Sep 17 00:00:00 2001 From: Refael Ackermann Date: Thu, 18 May 2017 00:43:09 -0400 Subject: [PATCH] async_wrap: add `asyncReset` to `TLSWrap` When using an Agent for HTTPS, `TLSSocket`s are reused and need to have the ability to `asyncReset` from JS. PR-URL: https://github.com/nodejs/node/pull/13092 Fixes: https://github.com/nodejs/node/issues/13045 Reviewed-By: Andreas Madsen Reviewed-By: Colin Ihrig Reviewed-By: Matteo Collina --- src/tls_wrap.cc | 1 + test/parallel/test-async-wrap-GH13045.js | 53 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 test/parallel/test-async-wrap-GH13045.js diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 05349b2f55230d..e6de9423710706 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -940,6 +940,7 @@ void TLSWrap::Initialize(Local target, t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "TLSWrap")); env->SetProtoMethod(t, "getAsyncId", AsyncWrap::GetAsyncId); + env->SetProtoMethod(t, "asyncReset", AsyncWrap::AsyncReset); env->SetProtoMethod(t, "receive", Receive); env->SetProtoMethod(t, "start", Start); env->SetProtoMethod(t, "setVerifyMode", SetVerifyMode); diff --git a/test/parallel/test-async-wrap-GH13045.js b/test/parallel/test-async-wrap-GH13045.js new file mode 100644 index 00000000000000..9fab1ee2ae1b0f --- /dev/null +++ b/test/parallel/test-async-wrap-GH13045.js @@ -0,0 +1,53 @@ +'use strict'; +const common = require('../common'); + +// Refs: https://github.com/nodejs/node/issues/13045 +// An HTTP Agent reuses a TLSSocket, and makes a failed call to `asyncReset`. + +const assert = require('assert'); +const https = require('https'); +const fs = require('fs'); + +const serverOptions = { + key: fs.readFileSync(`${common.fixturesDir}/keys/agent1-key.pem`), + cert: fs.readFileSync(`${common.fixturesDir}/keys/agent1-cert.pem`), + ca: fs.readFileSync(`${common.fixturesDir}/keys/ca1-cert.pem`) +}; + +const server = https.createServer(serverOptions, common.mustCall((req, res) => { + res.end('hello world\n'); +}, 2)); + +server.listen(0, common.mustCall(function() { + const port = this.address().port; + const clientOptions = { + agent: new https.Agent({ + keepAlive: true, + rejectUnauthorized: false + }), + port: port + }; + + const req = https.get(clientOptions, common.mustCall((res) => { + assert.strictEqual(res.statusCode, 200); + res.on('error', (err) => assert.fail(err)); + res.socket.on('error', (err) => assert.fail(err)); + res.resume(); + // drain the socket and wait for it to be free to reuse + res.socket.once('free', () => { + // This is the pain point. Internally the Agent will call + // `socket._handle.asyncReset()` and if the _handle does not implement + // `asyncReset` this will throw TypeError + const req2 = https.get(clientOptions, common.mustCall((res2) => { + assert.strictEqual(res.statusCode, 200); + res2.on('error', (err) => assert.fail(err)); + res2.socket.on('error', (err) => assert.fail(err)); + // this should be the end of the test + res2.destroy(); + server.close(); + })); + req2.on('error', (err) => assert.fail(err)); + }); + })); + req.on('error', (err) => assert.fail(err)); +}));