Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit aa1d8a9

Browse filesBrowse files
akshatsrivastava11aduh95
authored andcommitted
http: add req.signal to IncomingMessage
PR-URL: #62541 Fixes: #62481 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com>
1 parent 81bac1e commit aa1d8a9
Copy full SHA for aa1d8a9

3 files changed

+153Lines changed: 153 additions & 0 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎doc/api/http.md‎

Copy file name to clipboardExpand all lines: doc/api/http.md
+45Lines changed: 45 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -2968,6 +2968,51 @@ added: v0.5.9
29682968

29692969
Calls `message.socket.setTimeout(msecs, callback)`.
29702970

2971+
### `message.signal`
2972+
2973+
<!-- YAML
2974+
added: REPLACEME
2975+
-->
2976+
2977+
* Type: {AbortSignal}
2978+
2979+
An {AbortSignal} that is aborted when the underlying socket closes or the
2980+
request is destroyed. The signal is created lazily on first access — no
2981+
{AbortController} is allocated for requests that never use this property.
2982+
2983+
This is useful for cancelling downstream asynchronous work such as database
2984+
queries or `fetch` calls when a client disconnects mid-request.
2985+
2986+
```mjs
2987+
import http from 'node:http';
2988+
2989+
http.createServer(async (req, res) => {
2990+
try {
2991+
const data = await fetch('https://example.com/api', { signal: req.signal });
2992+
res.end(JSON.stringify(await data.json()));
2993+
} catch (err) {
2994+
if (err.name === 'AbortError') return;
2995+
res.statusCode = 500;
2996+
res.end('Internal Server Error');
2997+
}
2998+
}).listen(3000);
2999+
```
3000+
3001+
```cjs
3002+
const http = require('node:http');
3003+
3004+
http.createServer(async (req, res) => {
3005+
try {
3006+
const data = await fetch('https://example.com/api', { signal: req.signal });
3007+
res.end(JSON.stringify(await data.json()));
3008+
} catch (err) {
3009+
if (err.name === 'AbortError') return;
3010+
res.statusCode = 500;
3011+
res.end('Internal Server Error');
3012+
}
3013+
}).listen(3000);
3014+
```
3015+
29713016
### `message.socket`
29723017

29733018
<!-- YAML
Collapse file

‎lib/_http_incoming.js‎

Copy file name to clipboardExpand all lines: lib/_http_incoming.js
+23Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ const {
2929

3030
const { Readable, finished } = require('stream');
3131

32+
const { AbortController } = require('internal/abort_controller');
33+
3234
const kHeaders = Symbol('kHeaders');
3335
const kHeadersDistinct = Symbol('kHeadersDistinct');
3436
const kHeadersCount = Symbol('kHeadersCount');
3537
const kTrailers = Symbol('kTrailers');
3638
const kTrailersDistinct = Symbol('kTrailersDistinct');
3739
const kTrailersCount = Symbol('kTrailersCount');
40+
const kAbortController = Symbol('kAbortController');
3841

3942
function readStart(socket) {
4043
if (socket && !socket._paused && socket.readable)
@@ -90,6 +93,7 @@ function IncomingMessage(socket) {
9093
// Flag for when we decide that this message cannot possibly be
9194
// read by the user, so there's no point continuing to handle it.
9295
this._dumped = false;
96+
this[kAbortController] = null;
9397
}
9498
ObjectSetPrototypeOf(IncomingMessage.prototype, Readable.prototype);
9599
ObjectSetPrototypeOf(IncomingMessage, Readable);
@@ -184,6 +188,25 @@ ObjectDefineProperty(IncomingMessage.prototype, 'trailersDistinct', {
184188
},
185189
});
186190

191+
ObjectDefineProperty(IncomingMessage.prototype, 'signal', {
192+
__proto__: null,
193+
configurable: true,
194+
get: function() {
195+
if (this[kAbortController] === null) {
196+
const ac = new AbortController();
197+
this[kAbortController] = ac;
198+
if (this.destroyed) {
199+
ac.abort();
200+
} else {
201+
this.once('close', function() {
202+
ac.abort();
203+
});
204+
}
205+
}
206+
return this[kAbortController].signal;
207+
},
208+
});
209+
187210
IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
188211
if (callback)
189212
this.on('timeout', callback);
Collapse file
+85Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const http = require('http');
6+
7+
// Test 1: req.signal is an AbortSignal and aborts on 'close'
8+
{
9+
const server = http.createServer(common.mustCall((req, res) => {
10+
assert.ok(req.signal instanceof AbortSignal);
11+
assert.strictEqual(req.signal.aborted, false);
12+
req.signal.onabort = common.mustCall(() => {
13+
assert.strictEqual(req.signal.aborted, true);
14+
});
15+
res.destroy();
16+
}));
17+
server.listen(0, common.mustCall(() => {
18+
http.get({ port: server.address().port }, () => {}).on('error', () => {
19+
server.close();
20+
});
21+
}));
22+
}
23+
24+
// Test 2: req.signal is aborted if accessed after destroy
25+
{
26+
const req = new http.IncomingMessage(null);
27+
req.destroy();
28+
assert.strictEqual(req.signal.aborted, true);
29+
}
30+
31+
// Test 3: Multiple accesses return the same signal
32+
{
33+
const req = new http.IncomingMessage(null);
34+
assert.strictEqual(req.signal, req.signal);
35+
}
36+
37+
38+
// Test 4: res.signal on a client-side http.request() response (IncomingMessage).
39+
{
40+
const server = http.createServer(common.mustCall((req, res) => {
41+
res.writeHead(200);
42+
res.write('partial');
43+
}));
44+
45+
server.listen(0, common.mustCall(() => {
46+
const clientReq = http.request(
47+
{ port: server.address().port },
48+
common.mustCall((res) => {
49+
assert.ok(res.signal instanceof AbortSignal);
50+
assert.strictEqual(res.signal.aborted, false);
51+
52+
res.signal.onabort = common.mustCall(() => {
53+
assert.strictEqual(res.signal.aborted, true);
54+
server.close();
55+
});
56+
clientReq.destroy();
57+
}),
58+
);
59+
clientReq.on('error', () => {});
60+
clientReq.end();
61+
}));
62+
}
63+
64+
// Test 5: Client cancels a pending request.
65+
{
66+
const server = http.createServer(common.mustCall((req, res) => {
67+
req.signal.onabort = common.mustCall(() => {
68+
assert.strictEqual(req.signal.aborted, true);
69+
server.close();
70+
});
71+
res.flushHeaders();
72+
}));
73+
74+
server.listen(0, common.mustCall(() => {
75+
const clientReq = http.request(
76+
{ port: server.address().port },
77+
common.mustCall((res) => {
78+
res.on('error', () => {});
79+
clientReq.destroy();
80+
}),
81+
);
82+
clientReq.on('error', () => {});
83+
clientReq.end();
84+
}));
85+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.