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 9047ec1

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 ae41e2a commit 9047ec1
Copy full SHA for 9047ec1

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
@@ -2989,6 +2989,51 @@ added: v0.5.9
29892989

29902990
Calls `message.socket.setTimeout(msecs, callback)`.
29912991

2992+
### `message.signal`
2993+
2994+
<!-- YAML
2995+
added: REPLACEME
2996+
-->
2997+
2998+
* Type: {AbortSignal}
2999+
3000+
An {AbortSignal} that is aborted when the underlying socket closes or the
3001+
request is destroyed. The signal is created lazily on first access — no
3002+
{AbortController} is allocated for requests that never use this property.
3003+
3004+
This is useful for cancelling downstream asynchronous work such as database
3005+
queries or `fetch` calls when a client disconnects mid-request.
3006+
3007+
```mjs
3008+
import http from 'node:http';
3009+
3010+
http.createServer(async (req, res) => {
3011+
try {
3012+
const data = await fetch('https://example.com/api', { signal: req.signal });
3013+
res.end(JSON.stringify(await data.json()));
3014+
} catch (err) {
3015+
if (err.name === 'AbortError') return;
3016+
res.statusCode = 500;
3017+
res.end('Internal Server Error');
3018+
}
3019+
}).listen(3000);
3020+
```
3021+
3022+
```cjs
3023+
const http = require('node:http');
3024+
3025+
http.createServer(async (req, res) => {
3026+
try {
3027+
const data = await fetch('https://example.com/api', { signal: req.signal });
3028+
res.end(JSON.stringify(await data.json()));
3029+
} catch (err) {
3030+
if (err.name === 'AbortError') return;
3031+
res.statusCode = 500;
3032+
res.end('Internal Server Error');
3033+
}
3034+
}).listen(3000);
3035+
```
3036+
29923037
### `message.socket`
29933038

29943039
<!-- 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.