Skip to content

Commit fa57b72

Browse files
committed
http2: don't throw when destroying socket proxy
Calling destroy() on the http2session.socket proxy now destroys the session instead of throwing ERR_HTTP2_NO_SOCKET_MANIPULATION. Refs: nodejs/undici#5525 Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 8a3b11c commit fa57b72

4 files changed

Lines changed: 69 additions & 6 deletions

File tree

doc/api/http2.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -722,18 +722,24 @@ registered as a listener on the `'timeout'` event.
722722

723723
<!-- YAML
724724
added: v8.4.0
725+
changes:
726+
- version: REPLACEME
727+
pr-url: https://github.com/nodejs/node/pull/64427
728+
description: Calling `destroy` no longer throws and instead destroys the
729+
`Http2Session`.
725730
-->
726731

727732
* Type: {net.Socket|tls.TLSSocket}
728733

729734
Returns a `Proxy` object that acts as a `net.Socket` (or `tls.TLSSocket`) but
730735
limits available methods to ones safe to use with HTTP/2.
731736

732-
`destroy`, `emit`, `end`, `pause`, `read`, `resume`, and `write` will throw
737+
`emit`, `end`, `pause`, `read`, `resume`, and `write` will throw
733738
an error with code `ERR_HTTP2_NO_SOCKET_MANIPULATION`. See
734739
[`Http2Session` and Sockets][] for more information.
735740

736-
`setTimeout` method will be called on this `Http2Session`.
741+
`destroy`, `setTimeout`, `ref`, and `unref` methods will be called on this
742+
`Http2Session`.
737743

738744
All other interactions will be routed directly to the socket.
739745

lib/internal/http2/core.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -984,8 +984,8 @@ const proxySocketHandler = {
984984
case 'setTimeout':
985985
case 'ref':
986986
case 'unref':
987-
return session[prop].bind(session);
988987
case 'destroy':
988+
return session[prop].bind(session);
989989
case 'emit':
990990
case 'end':
991991
case 'pause':
@@ -1018,9 +1018,9 @@ const proxySocketHandler = {
10181018
case 'setTimeout':
10191019
case 'ref':
10201020
case 'unref':
1021+
case 'destroy':
10211022
session[prop] = value;
10221023
return true;
1023-
case 'destroy':
10241024
case 'emit':
10251025
case 'end':
10261026
case 'pause':
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
const assert = require('assert');
7+
const h2 = require('http2');
8+
9+
// Calling .destroy() on the socket proxy must not throw
10+
// (previously threw ERR_HTTP2_NO_SOCKET_MANIPULATION) and must
11+
// destroy the Http2Session and the underlying socket.
12+
13+
const server = h2.createServer();
14+
15+
server.on('stream', common.mustCall(function(stream) {
16+
stream.respond();
17+
stream.end('ok');
18+
}, 2));
19+
20+
server.listen(0, common.mustCall(() => {
21+
const port = server.address().port;
22+
23+
// Test destroy() without an error.
24+
{
25+
const client = h2.connect(`http://localhost:${port}`);
26+
const request = client.request();
27+
request.resume();
28+
request.on('close', common.mustCall(() => {
29+
const socket = client.socket;
30+
socket.destroy();
31+
assert.strictEqual(client.destroyed, true);
32+
client.on('close', common.mustCall(() => {
33+
assert.strictEqual(client.socket, undefined);
34+
// Destroying again through a stale proxy reference must not throw.
35+
socket.destroy();
36+
}));
37+
}));
38+
client.on('error', common.mustNotCall());
39+
}
40+
41+
// Test destroy() with an error.
42+
{
43+
const client = h2.connect(`http://localhost:${port}`);
44+
const request = client.request();
45+
request.resume();
46+
request.on('close', common.mustCall(() => {
47+
client.socket.destroy(new Error('boom'));
48+
assert.strictEqual(client.destroyed, true);
49+
}));
50+
client.on('error', common.mustCall((err) => {
51+
assert.strictEqual(err.message, 'boom');
52+
}));
53+
client.on('close', common.mustCall(() => {
54+
server.close();
55+
}));
56+
}
57+
}));

test/parallel/test-http2-socket-proxy.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ server.on('stream', common.mustCall(function(stream, headers) {
5252
assert(inspectedTimersList.includes(' _idleNext: [Timeout]'));
5353
assert(!inspectedTimersList.includes(' _idleNext: [Timeout]'));
5454

55-
assert.throws(() => socket.destroy, errMsg);
55+
assert.strictEqual(typeof socket.destroy, 'function');
56+
5657
assert.throws(() => socket.emit, errMsg);
5758
assert.throws(() => socket.end, errMsg);
5859
assert.throws(() => socket.pause, errMsg);
@@ -63,7 +64,6 @@ server.on('stream', common.mustCall(function(stream, headers) {
6364
assert.throws(() => socket.setKeepAlive, errMsg);
6465
assert.throws(() => socket.setNoDelay, errMsg);
6566

66-
assert.throws(() => (socket.destroy = undefined), errMsg);
6767
assert.throws(() => (socket.emit = undefined), errMsg);
6868
assert.throws(() => (socket.end = undefined), errMsg);
6969
assert.throws(() => (socket.pause = undefined), errMsg);

0 commit comments

Comments
 (0)