| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2966,6 +2966,51 @@ added: v0.5.9 | |||
| 2966 | 2966 | ||
| 2967 | 2967 | Calls `message.socket.setTimeout(msecs, callback)`. | |
| 2968 | 2968 | ||
| 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 | + import http from 'node:http'; | ||
| 2986 | + | ||
| 2987 | + http.createServer(async (req, res) => { | ||
| 2988 | + try { | ||
| 2989 | + const data = await fetch('https://example.com/api', { signal: req.signal }); | ||
| 2990 | + res.end(JSON.stringify(await data.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 | + const http = require('node:http'); | ||
| 3001 | + | ||
| 3002 | + http.createServer(async (req, res) => { | ||
| 3003 | + try { | ||
| 3004 | + const data = await fetch('https://example.com/api', { signal: req.signal }); | ||
| 3005 | + res.end(JSON.stringify(await data.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 | + | ||
| 2969 | 3014 | ### `message.socket` | |
| 2970 | 3015 | ||
| 2971 | 3016 | <!-- YAML | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,12 +29,15 @@ const { | |||
| 29 | 29 | ||
| 30 | 30 | const { Readable, finished } = require('stream'); | |
| 31 | 31 | ||
| 32 | + const { AbortController } = require('internal/abort_controller'); | ||
| 33 | + | ||
| 32 | 34 | const kHeaders = Symbol('kHeaders'); | |
| 33 | 35 | const kHeadersDistinct = Symbol('kHeadersDistinct'); | |
| 34 | 36 | const kHeadersCount = Symbol('kHeadersCount'); | |
| 35 | 37 | const kTrailers = Symbol('kTrailers'); | |
| 36 | 38 | const kTrailersDistinct = Symbol('kTrailersDistinct'); | |
| 37 | 39 | const kTrailersCount = Symbol('kTrailersCount'); | |
| 40 | + const kAbortController = Symbol('kAbortController'); | ||
| 38 | 41 | ||
| 39 | 42 | function readStart(socket) { | |
| 40 | 43 | if (socket && !socket._paused && socket.readable) | |
@@ -90,6 +93,7 @@ function IncomingMessage(socket) { | |||
| 90 | 93 | // Flag for when we decide that this message cannot possibly be | |
| 91 | 94 | // read by the user, so there's no point continuing to handle it. | |
| 92 | 95 | this._dumped = false; | |
| 96 | + this[kAbortController] = null; | ||
| 93 | 97 | } | |
| 94 | 98 | ObjectSetPrototypeOf(IncomingMessage.prototype, Readable.prototype); | |
| 95 | 99 | ObjectSetPrototypeOf(IncomingMessage, Readable); | |
@@ -184,6 +188,25 @@ ObjectDefineProperty(IncomingMessage.prototype, 'trailersDistinct', { | |||
| 184 | 188 | }, | |
| 185 | 189 | }); | |
| 186 | 190 | ||
| 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 | + | ||
| 187 | 210 | IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) { | |
| 188 | 211 | if (callback) | |
| 189 | 212 | this.on('timeout', callback); | |
| Original file line number | Diff line number | Diff 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 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments