| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 524b713 commit 336fb18
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,9 @@ | |||
| 2 | 2 | <!-- YAML | |
| 3 | 3 | added: v8.4.0 | |
| 4 | 4 | changes: | |
| 5 | + - version: REPLACEME | ||
| 6 | + pr-url: https://github.com/nodejs/node/pull/36070 | ||
| 7 | + description: It is possible to abort a request with an AbortSignal. | ||
| 5 | 8 | - version: v10.10.0 | |
| 6 | 9 | pr-url: https://github.com/nodejs/node/pull/22466 | |
| 7 | 10 | description: HTTP/2 is now Stable. Previously, it had been Experimental. | |
@@ -819,6 +822,8 @@ added: v8.4.0 | |||
| 819 | 822 | and `256` (inclusive). | |
| 820 | 823 | * `waitForTrailers` {boolean} When `true`, the `Http2Stream` will emit the | |
| 821 | 824 | `'wantTrailers'` event after the final `DATA` frame has been sent. | |
| 825 | + * `signal` {AbortSignal} An AbortSignal that may be used to abort an ongoing | ||
| 826 | + request. | ||
| 822 | 827 | ||
| 823 | 828 | * Returns: {ClientHttp2Stream} | |
| 824 | 829 | ||
@@ -855,6 +860,10 @@ close when the final `DATA` frame is transmitted. User code must call either | |||
| 855 | 860 | `http2stream.sendTrailers()` or `http2stream.close()` to close the | |
| 856 | 861 | `Http2Stream`. | |
| 857 | 862 | ||
| 863 | + When `options.signal` is set with an `AbortSignal` and then `abort` on the | ||
| 864 | + corresponding `AbortController` is called, the request will emit an `'error'` | ||
| 865 | + event with an `AbortError` error. | ||
| 866 | + | ||
| 858 | 867 | The `:method` and `:path` pseudo-headers are not specified within `headers`, | |
| 859 | 868 | they respectively default to: | |
| 860 | 869 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -94,12 +94,15 @@ const { | |||
| 94 | 94 | ERR_OUT_OF_RANGE, | |
| 95 | 95 | ERR_SOCKET_CLOSED | |
| 96 | 96 | }, | |
| 97 | - hideStackFrames | ||
| 97 | + hideStackFrames, | ||
| 98 | + AbortError | ||
| 98 | 99 | } = require('internal/errors'); | |
| 99 | - const { validateNumber, | ||
| 100 | - validateString, | ||
| 101 | - validateUint32, | ||
| 102 | - isUint32, | ||
| 100 | + const { | ||
| 101 | + isUint32, | ||
| 102 | + validateNumber, | ||
| 103 | + validateString, | ||
| 104 | + validateUint32, | ||
| 105 | + validateAbortSignal, | ||
| 103 | 106 | } = require('internal/validators'); | |
| 104 | 107 | const fsPromisesInternal = require('internal/fs/promises'); | |
| 105 | 108 | const { utcDate } = require('internal/http'); | |
@@ -1666,6 +1669,20 @@ class ClientHttp2Session extends Http2Session { | |||
| 1666 | 1669 | if (options.waitForTrailers) | |
| 1667 | 1670 | stream[kState].flags |= STREAM_FLAGS_HAS_TRAILERS; | |
| 1668 | 1671 | ||
| 1672 | + const { signal } = options; | ||
| 1673 | + if (signal) { | ||
| 1674 | + validateAbortSignal(signal, 'options.signal'); | ||
| 1675 | + const aborter = () => stream.destroy(new AbortError()); | ||
| 1676 | + if (signal.aborted) { | ||
| 1677 | + aborter(); | ||
| 1678 | + } else { | ||
| 1679 | + signal.addEventListener('abort', aborter); | ||
| 1680 | + stream.once('close', () => { | ||
| 1681 | + signal.removeEventListener('abort', aborter); | ||
| 1682 | + }); | ||
| 1683 | + } | ||
| 1684 | + } | ||
| 1685 | + | ||
| 1669 | 1686 | const onConnect = requestOnConnect.bind(stream, headersList, options); | |
| 1670 | 1687 | if (this.connecting) { | |
| 1671 | 1688 | if (this[kPendingRequestCalls] !== null) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,4 @@ | |||
| 1 | - // Flags: --expose-internals | ||
| 1 | + // Flags: --expose-internals --experimental-abortcontroller | ||
| 2 | 2 | ||
| 3 | 3 | 'use strict'; | |
| 4 | 4 | ||
@@ -8,6 +8,7 @@ if (!common.hasCrypto) | |||
| 8 | 8 | const assert = require('assert'); | |
| 9 | 9 | const h2 = require('http2'); | |
| 10 | 10 | const { kSocket } = require('internal/http2/util'); | |
| 11 | + const { kEvents } = require('internal/event_target'); | ||
| 11 | 12 | const Countdown = require('../common/countdown'); | |
| 12 | 13 | ||
| 13 | 14 | { | |
@@ -165,3 +166,76 @@ const Countdown = require('../common/countdown'); | |||
| 165 | 166 | req.on('close', common.mustCall(() => server.close())); | |
| 166 | 167 | })); | |
| 167 | 168 | } | |
| 169 | + | ||
| 170 | + // Destroy with AbortSignal | ||
| 171 | + { | ||
| 172 | + const server = h2.createServer(); | ||
| 173 | + const controller = new AbortController(); | ||
| 174 | + | ||
| 175 | + server.on('stream', common.mustNotCall()); | ||
| 176 | + server.listen(0, common.mustCall(() => { | ||
| 177 | + const client = h2.connect(`http://localhost:${server.address().port}`); | ||
| 178 | + client.on('close', common.mustCall()); | ||
| 179 | + | ||
| 180 | + const { signal } = controller; | ||
| 181 | + assert.strictEqual(signal[kEvents].get('abort'), undefined); | ||
| 182 | + | ||
| 183 | + client.on('error', common.mustCall(() => { | ||
| 184 | + // After underlying stream dies, signal listener detached | ||
| 185 | + assert.strictEqual(signal[kEvents].get('abort'), undefined); | ||
| 186 | + })); | ||
| 187 | + | ||
| 188 | + const req = client.request({}, { signal }); | ||
| 189 | + | ||
| 190 | + req.on('error', common.mustCall((err) => { | ||
| 191 | + assert.strictEqual(err.code, 'ABORT_ERR'); | ||
| 192 | + assert.strictEqual(err.name, 'AbortError'); | ||
| 193 | + })); | ||
| 194 | + req.on('close', common.mustCall(() => server.close())); | ||
| 195 | + | ||
| 196 | + assert.strictEqual(req.aborted, false); | ||
| 197 | + assert.strictEqual(req.destroyed, false); | ||
| 198 | + // Signal listener attached | ||
| 199 | + assert.strictEqual(signal[kEvents].get('abort').size, 1); | ||
| 200 | + | ||
| 201 | + controller.abort(); | ||
| 202 | + | ||
| 203 | + assert.strictEqual(req.aborted, false); | ||
| 204 | + assert.strictEqual(req.destroyed, true); | ||
| 205 | + })); | ||
| 206 | + } | ||
| 207 | + // Pass an already destroyed signal to abort immediately. | ||
| 208 | + { | ||
| 209 | + const server = h2.createServer(); | ||
| 210 | + const controller = new AbortController(); | ||
| 211 | + | ||
| 212 | + server.on('stream', common.mustNotCall()); | ||
| 213 | + server.listen(0, common.mustCall(() => { | ||
| 214 | + const client = h2.connect(`http://localhost:${server.address().port}`); | ||
| 215 | + client.on('close', common.mustCall()); | ||
| 216 | + | ||
| 217 | + const { signal } = controller; | ||
| 218 | + controller.abort(); | ||
| 219 | + | ||
| 220 | + assert.strictEqual(signal[kEvents].get('abort'), undefined); | ||
| 221 | + | ||
| 222 | + client.on('error', common.mustCall(() => { | ||
| 223 | + // After underlying stream dies, signal listener detached | ||
| 224 | + assert.strictEqual(signal[kEvents].get('abort'), undefined); | ||
| 225 | + })); | ||
| 226 | + | ||
| 227 | + const req = client.request({}, { signal }); | ||
| 228 | + // Signal already aborted, so no event listener attached. | ||
| 229 | + assert.strictEqual(signal[kEvents].get('abort'), undefined); | ||
| 230 | + | ||
| 231 | + assert.strictEqual(req.aborted, false); | ||
| 232 | + // Destroyed on same tick as request made | ||
| 233 | + assert.strictEqual(req.destroyed, true); | ||
| 234 | + | ||
| 235 | + req.on('error', common.mustCall((err) => { | ||
| 236 | + assert.strictEqual(err.code, 'ABORT_ERR'); | ||
| 237 | + assert.strictEqual(err.name, 'AbortError'); | ||
| 238 | + })); | ||
| 239 | + req.on('close', common.mustCall(() => server.close())); | ||
| 240 | + })); | ||
| 241 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments