mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
http2: add support for AbortSignal to http2Session.request
- Add support - Add test - Docs once PR is up PR-URL: https://github.com/nodejs/node/pull/36070 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
committed by
Benjamin Gruenbaum
parent
03fd363472
commit
630afc3703
@@ -2,6 +2,9 @@
|
||||
<!-- YAML
|
||||
added: v8.4.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/36070
|
||||
description: It is possible to abort a request with an AbortSignal.
|
||||
- version: v15.0.0
|
||||
pr-url: https://github.com/nodejs/node/pull/34664
|
||||
description: Requests with the `host` header (with or without
|
||||
@@ -846,6 +849,8 @@ added: v8.4.0
|
||||
and `256` (inclusive).
|
||||
* `waitForTrailers` {boolean} When `true`, the `Http2Stream` will emit the
|
||||
`'wantTrailers'` event after the final `DATA` frame has been sent.
|
||||
* `signal` {AbortSignal} An AbortSignal that may be used to abort an ongoing
|
||||
request.
|
||||
|
||||
* Returns: {ClientHttp2Stream}
|
||||
|
||||
@@ -882,6 +887,10 @@ close when the final `DATA` frame is transmitted. User code must call either
|
||||
`http2stream.sendTrailers()` or `http2stream.close()` to close the
|
||||
`Http2Stream`.
|
||||
|
||||
When `options.signal` is set with an `AbortSignal` and then `abort` on the
|
||||
corresponding `AbortController` is called, the request will emit an `'error'`
|
||||
event with an `AbortError` error.
|
||||
|
||||
The `:method` and `:path` pseudo-headers are not specified within `headers`,
|
||||
they respectively default to:
|
||||
|
||||
|
||||
@@ -109,7 +109,8 @@ const {
|
||||
ERR_OUT_OF_RANGE,
|
||||
ERR_SOCKET_CLOSED
|
||||
},
|
||||
hideStackFrames
|
||||
hideStackFrames,
|
||||
AbortError
|
||||
} = require('internal/errors');
|
||||
const {
|
||||
isUint32,
|
||||
@@ -118,6 +119,7 @@ const {
|
||||
validateNumber,
|
||||
validateString,
|
||||
validateUint32,
|
||||
validateAbortSignal,
|
||||
} = require('internal/validators');
|
||||
const fsPromisesInternal = require('internal/fs/promises');
|
||||
const { utcDate } = require('internal/http');
|
||||
@@ -1721,6 +1723,20 @@ class ClientHttp2Session extends Http2Session {
|
||||
if (options.waitForTrailers)
|
||||
stream[kState].flags |= STREAM_FLAGS_HAS_TRAILERS;
|
||||
|
||||
const { signal } = options;
|
||||
if (signal) {
|
||||
validateAbortSignal(signal, 'options.signal');
|
||||
const aborter = () => stream.destroy(new AbortError());
|
||||
if (signal.aborted) {
|
||||
aborter();
|
||||
} else {
|
||||
signal.addEventListener('abort', aborter);
|
||||
stream.once('close', () => {
|
||||
signal.removeEventListener('abort', aborter);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const onConnect = FunctionPrototypeBind(requestOnConnect,
|
||||
stream, headersList, options);
|
||||
if (this.connecting) {
|
||||
|
||||
@@ -8,6 +8,7 @@ if (!common.hasCrypto)
|
||||
const assert = require('assert');
|
||||
const h2 = require('http2');
|
||||
const { kSocket } = require('internal/http2/util');
|
||||
const { kEvents } = require('internal/event_target');
|
||||
const Countdown = require('../common/countdown');
|
||||
|
||||
{
|
||||
@@ -167,3 +168,76 @@ const Countdown = require('../common/countdown');
|
||||
req.on('close', common.mustCall(() => server.close()));
|
||||
}));
|
||||
}
|
||||
|
||||
// Destroy with AbortSignal
|
||||
{
|
||||
const server = h2.createServer();
|
||||
const controller = new AbortController();
|
||||
|
||||
server.on('stream', common.mustNotCall());
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const client = h2.connect(`http://localhost:${server.address().port}`);
|
||||
client.on('close', common.mustCall());
|
||||
|
||||
const { signal } = controller;
|
||||
assert.strictEqual(signal[kEvents].get('abort'), undefined);
|
||||
|
||||
client.on('error', common.mustCall(() => {
|
||||
// After underlying stream dies, signal listener detached
|
||||
assert.strictEqual(signal[kEvents].get('abort'), undefined);
|
||||
}));
|
||||
|
||||
const req = client.request({}, { signal });
|
||||
|
||||
req.on('error', common.mustCall((err) => {
|
||||
assert.strictEqual(err.code, 'ABORT_ERR');
|
||||
assert.strictEqual(err.name, 'AbortError');
|
||||
}));
|
||||
req.on('close', common.mustCall(() => server.close()));
|
||||
|
||||
assert.strictEqual(req.aborted, false);
|
||||
assert.strictEqual(req.destroyed, false);
|
||||
// Signal listener attached
|
||||
assert.strictEqual(signal[kEvents].get('abort').size, 1);
|
||||
|
||||
controller.abort();
|
||||
|
||||
assert.strictEqual(req.aborted, false);
|
||||
assert.strictEqual(req.destroyed, true);
|
||||
}));
|
||||
}
|
||||
// Pass an already destroyed signal to abort immediately.
|
||||
{
|
||||
const server = h2.createServer();
|
||||
const controller = new AbortController();
|
||||
|
||||
server.on('stream', common.mustNotCall());
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const client = h2.connect(`http://localhost:${server.address().port}`);
|
||||
client.on('close', common.mustCall());
|
||||
|
||||
const { signal } = controller;
|
||||
controller.abort();
|
||||
|
||||
assert.strictEqual(signal[kEvents].get('abort'), undefined);
|
||||
|
||||
client.on('error', common.mustCall(() => {
|
||||
// After underlying stream dies, signal listener detached
|
||||
assert.strictEqual(signal[kEvents].get('abort'), undefined);
|
||||
}));
|
||||
|
||||
const req = client.request({}, { signal });
|
||||
// Signal already aborted, so no event listener attached.
|
||||
assert.strictEqual(signal[kEvents].get('abort'), undefined);
|
||||
|
||||
assert.strictEqual(req.aborted, false);
|
||||
// Destroyed on same tick as request made
|
||||
assert.strictEqual(req.destroyed, true);
|
||||
|
||||
req.on('error', common.mustCall((err) => {
|
||||
assert.strictEqual(err.code, 'ABORT_ERR');
|
||||
assert.strictEqual(err.name, 'AbortError');
|
||||
}));
|
||||
req.on('close', common.mustCall(() => server.close()));
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user