Skip to content

Commit d95374b

Browse files
akshatsrivastava11juanarbol
authored andcommitted
http: add req.signal to IncomingMessage
PR-URL: #62541Fixes: #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 73e117d commit d95374b

3 files changed

Lines changed: 153 additions & 0 deletions

File tree

‎doc/api/http.md‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2966,6 +2966,51 @@ added: v0.5.9
29662966

29672967
Calls `message.socket.setTimeout(msecs, callback)`.
29682968

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

29713016
<!-- YAML

‎lib/_http_incoming.js‎

Lines 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
constkHeaders=Symbol('kHeaders');
3335
constkHeadersDistinct=Symbol('kHeadersDistinct');
3436
constkHeadersCount=Symbol('kHeadersCount');
3537
constkTrailers=Symbol('kTrailers');
3638
constkTrailersDistinct=Symbol('kTrailersDistinct');
3739
constkTrailersCount=Symbol('kTrailersCount');
40+
constkAbortController=Symbol('kAbortController');
3841

3942
functionreadStart(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+
constac=newAbortController();
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+
returnthis[kAbortController].signal;
207+
},
208+
});
209+
187210
IncomingMessage.prototype.setTimeout=functionsetTimeout(msecs,callback){
188211
if(callback)
189212
this.on('timeout',callback);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
constassert=require('assert');
5+
consthttp=require('http');
6+
7+
// Test 1: req.signal is an AbortSignal and aborts on 'close'
8+
{
9+
constserver=http.createServer(common.mustCall((req,res)=>{
10+
assert.ok(req.signalinstanceofAbortSignal);
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+
constreq=newhttp.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+
constreq=newhttp.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+
constserver=http.createServer(common.mustCall((req,res)=>{
41+
res.writeHead(200);
42+
res.write('partial');
43+
}));
44+
45+
server.listen(0,common.mustCall(()=>{
46+
constclientReq=http.request(
47+
{port: server.address().port},
48+
common.mustCall((res)=>{
49+
assert.ok(res.signalinstanceofAbortSignal);
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+
constserver=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+
constclientReq=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)