mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
Keep the original socket error as the cause of ECONNRESET errors emitted when an HTTP response closes before completion. Preserve the existing aborted message, error code, and event order. Leave cause absent when there is no underlying error. Allow ConnResetException to accept Error options, document the behavior, and cover TLS record errors, TCP resets, explicit destruction, and premature closure without a socket error. This follows investigation of #66001 and addresses lost error context. The original TLS decryption failure remains unresolved. Refs: https://github.com/nodejs/node/issues/66001 Signed-off-by: XadillaX <i@2333.moe> PR-URL: https://github.com/nodejs/node/pull/66061 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Tim Perry <pimterry@gmail.com>
84 lines
2.7 KiB
JavaScript
84 lines
2.7 KiB
JavaScript
'use strict';
|
|
|
|
const common = require('../common');
|
|
if (!common.hasCrypto)
|
|
common.skip('missing crypto');
|
|
|
|
const assert = require('assert');
|
|
const https = require('https');
|
|
const { Writable, pipeline } = require('stream');
|
|
const fixtures = require('../common/fixtures');
|
|
|
|
// Verify error propagation after receiving response headers. The invalid
|
|
// record deliberately causes a TLS error; it does not reproduce #66001's
|
|
// failure to decrypt an intact stream under backpressure.
|
|
for (const version of ['TLSv1.2', 'TLSv1.3']) {
|
|
let transport;
|
|
let requestError;
|
|
const events = [];
|
|
const server = https.createServer({
|
|
key: fixtures.readKey('agent1-key.pem'),
|
|
cert: fixtures.readKey('agent1-cert.pem'),
|
|
minVersion: version,
|
|
maxVersion: version,
|
|
}, common.mustCall((req, res) => {
|
|
res.writeHead(200, { 'Content-Length': 100 });
|
|
res.write('partial body');
|
|
}));
|
|
server.on('connection', common.mustCall((socket) => {
|
|
transport = socket;
|
|
}));
|
|
|
|
server.listen(0, common.mustCall(() => {
|
|
const req = https.get({
|
|
port: server.address().port,
|
|
rejectUnauthorized: false,
|
|
agent: false,
|
|
}, common.mustCall((res) => {
|
|
assert.strictEqual(res.complete, false);
|
|
res.on('aborted', common.mustCall(() => events.push('aborted')));
|
|
res.on('error', common.mustCall((err) => {
|
|
events.push('response error');
|
|
assert.strictEqual(err.code, 'ECONNRESET');
|
|
assert.strictEqual(err.message, 'aborted');
|
|
assert.strictEqual(err.cause, requestError);
|
|
}));
|
|
res.on('close', common.mustCall(() => {
|
|
events.push('response close');
|
|
assert.deepStrictEqual(events, [
|
|
'request error',
|
|
'aborted',
|
|
'request close',
|
|
'response error',
|
|
'response close',
|
|
]);
|
|
}));
|
|
|
|
pipeline(res, new Writable({
|
|
write(chunk, encoding, callback) {
|
|
callback();
|
|
},
|
|
}), common.mustCall((err) => {
|
|
assert.strictEqual(err, res.errored);
|
|
assert.strictEqual(err.cause, requestError);
|
|
assert.strictEqual(res.complete, false);
|
|
assert.strictEqual(res.aborted, true);
|
|
server.close(common.mustCall());
|
|
transport.destroy();
|
|
}));
|
|
|
|
// Bypass the server's TLSSocket to send an invalid application-data
|
|
// record after the client has received part of the HTTP response.
|
|
const record = Buffer.alloc(37);
|
|
record.set([23, 3, 3, 0, 32]);
|
|
transport.write(record);
|
|
}));
|
|
req.on('error', common.mustCall((err) => {
|
|
events.push('request error');
|
|
assert.match(err.code, /^ERR_SSL_/);
|
|
requestError = err;
|
|
}));
|
|
req.on('close', common.mustCall(() => events.push('request close')));
|
|
}));
|
|
}
|