mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
http2: allow setting the local window size of a session
PR-URL: https://github.com/nodejs/node/pull/35978 Fixes: https://github.com/nodejs/node/issues/31084 Refs: https://github.com/nodejs/node/pull/26962 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Ricky Zhou <0x19951125@gmail.com>
This commit is contained in:
parent
f6c797811d
commit
6fd5cd5b29
|
|
@ -1137,6 +1137,11 @@ reached.
|
||||||
An attempt was made to initiate a new push stream from within a push stream.
|
An attempt was made to initiate a new push stream from within a push stream.
|
||||||
Nested push streams are not permitted.
|
Nested push streams are not permitted.
|
||||||
|
|
||||||
|
<a id="ERR_HTTP2_NO_MEM"></a>
|
||||||
|
### `ERR_HTTP2_NO_MEM`
|
||||||
|
|
||||||
|
Out of memory when using the `http2session.setLocalWindowSize(windowSize)` API.
|
||||||
|
|
||||||
<a id="ERR_HTTP2_NO_SOCKET_MANIPULATION"></a>
|
<a id="ERR_HTTP2_NO_SOCKET_MANIPULATION"></a>
|
||||||
### `ERR_HTTP2_NO_SOCKET_MANIPULATION`
|
### `ERR_HTTP2_NO_SOCKET_MANIPULATION`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -518,6 +518,29 @@ added: v8.4.0
|
||||||
A prototype-less object describing the current remote settings of this
|
A prototype-less object describing the current remote settings of this
|
||||||
`Http2Session`. The remote settings are set by the *connected* HTTP/2 peer.
|
`Http2Session`. The remote settings are set by the *connected* HTTP/2 peer.
|
||||||
|
|
||||||
|
#### `http2session.setLocalWindowSize(windowSize)`
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `windowSize` {number}
|
||||||
|
|
||||||
|
Sets the local endpoint's window size.
|
||||||
|
The `windowSize` is the total window size to set, not
|
||||||
|
the delta.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const http2 = require('http2');
|
||||||
|
|
||||||
|
const server = http2.createServer();
|
||||||
|
const expectedWindowSize = 2 ** 20;
|
||||||
|
server.on('connect', (session) => {
|
||||||
|
|
||||||
|
// Set local window size to be 2 ** 20
|
||||||
|
session.setLocalWindowSize(expectedWindowSize);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
#### `http2session.setTimeout(msecs, callback)`
|
#### `http2session.setTimeout(msecs, callback)`
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v8.4.0
|
added: v8.4.0
|
||||||
|
|
|
||||||
|
|
@ -917,6 +917,7 @@ E('ERR_HTTP2_MAX_PENDING_SETTINGS_ACK',
|
||||||
'Maximum number of pending settings acknowledgements', Error);
|
'Maximum number of pending settings acknowledgements', Error);
|
||||||
E('ERR_HTTP2_NESTED_PUSH',
|
E('ERR_HTTP2_NESTED_PUSH',
|
||||||
'A push stream cannot initiate another push stream.', Error);
|
'A push stream cannot initiate another push stream.', Error);
|
||||||
|
E('ERR_HTTP2_NO_MEM', 'Out of memory', Error);
|
||||||
E('ERR_HTTP2_NO_SOCKET_MANIPULATION',
|
E('ERR_HTTP2_NO_SOCKET_MANIPULATION',
|
||||||
'HTTP/2 sockets should not be directly manipulated (e.g. read and written)',
|
'HTTP/2 sockets should not be directly manipulated (e.g. read and written)',
|
||||||
Error);
|
Error);
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ const {
|
||||||
ERR_HTTP2_INVALID_STREAM,
|
ERR_HTTP2_INVALID_STREAM,
|
||||||
ERR_HTTP2_MAX_PENDING_SETTINGS_ACK,
|
ERR_HTTP2_MAX_PENDING_SETTINGS_ACK,
|
||||||
ERR_HTTP2_NESTED_PUSH,
|
ERR_HTTP2_NESTED_PUSH,
|
||||||
|
ERR_HTTP2_NO_MEM,
|
||||||
ERR_HTTP2_NO_SOCKET_MANIPULATION,
|
ERR_HTTP2_NO_SOCKET_MANIPULATION,
|
||||||
ERR_HTTP2_ORIGIN_LENGTH,
|
ERR_HTTP2_ORIGIN_LENGTH,
|
||||||
ERR_HTTP2_OUT_OF_STREAMS,
|
ERR_HTTP2_OUT_OF_STREAMS,
|
||||||
|
|
@ -104,6 +105,7 @@ const {
|
||||||
} = require('internal/errors');
|
} = require('internal/errors');
|
||||||
const {
|
const {
|
||||||
isUint32,
|
isUint32,
|
||||||
|
validateInt32,
|
||||||
validateNumber,
|
validateNumber,
|
||||||
validateString,
|
validateString,
|
||||||
validateUint32,
|
validateUint32,
|
||||||
|
|
@ -252,6 +254,7 @@ const {
|
||||||
NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE,
|
NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE,
|
||||||
NGHTTP2_ERR_INVALID_ARGUMENT,
|
NGHTTP2_ERR_INVALID_ARGUMENT,
|
||||||
NGHTTP2_ERR_STREAM_CLOSED,
|
NGHTTP2_ERR_STREAM_CLOSED,
|
||||||
|
NGHTTP2_ERR_NOMEM,
|
||||||
|
|
||||||
HTTP2_HEADER_AUTHORITY,
|
HTTP2_HEADER_AUTHORITY,
|
||||||
HTTP2_HEADER_DATE,
|
HTTP2_HEADER_DATE,
|
||||||
|
|
@ -1290,6 +1293,21 @@ class Http2Session extends EventEmitter {
|
||||||
this[kHandle].setNextStreamID(id);
|
this[kHandle].setNextStreamID(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets the local window size (local endpoints's window size)
|
||||||
|
// Returns 0 if sucess or throw an exception if NGHTTP2_ERR_NOMEM
|
||||||
|
// if the window allocation fails
|
||||||
|
setLocalWindowSize(windowSize) {
|
||||||
|
if (this.destroyed)
|
||||||
|
throw new ERR_HTTP2_INVALID_SESSION();
|
||||||
|
|
||||||
|
validateInt32(windowSize, 'windowSize', 0);
|
||||||
|
const ret = this[kHandle].setLocalWindowSize(windowSize);
|
||||||
|
|
||||||
|
if (ret === NGHTTP2_ERR_NOMEM) {
|
||||||
|
this.destroy(new ERR_HTTP2_NO_MEM());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If ping is called while we are still connecting, or after close() has
|
// If ping is called while we are still connecting, or after close() has
|
||||||
// been called, the ping callback will be invoked immediately with a ping
|
// been called, the ping callback will be invoked immediately with a ping
|
||||||
// cancelled error and a duration of 0.0.
|
// cancelled error and a duration of 0.0.
|
||||||
|
|
|
||||||
|
|
@ -2433,6 +2433,25 @@ void Http2Session::SetNextStreamID(const FunctionCallbackInfo<Value>& args) {
|
||||||
Debug(session, "set next stream id to %d", id);
|
Debug(session, "set next stream id to %d", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set local window size (local endpoints's window size) to the given
|
||||||
|
// window_size for the stream denoted by 0.
|
||||||
|
// This function returns 0 if it succeeds, or one of a negative codes
|
||||||
|
void Http2Session::SetLocalWindowSize(
|
||||||
|
const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
Http2Session* session;
|
||||||
|
ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
|
||||||
|
|
||||||
|
int32_t window_size = args[0]->Int32Value(env->context()).ToChecked();
|
||||||
|
|
||||||
|
int result = nghttp2_session_set_local_window_size(
|
||||||
|
session->session(), NGHTTP2_FLAG_NONE, 0, window_size);
|
||||||
|
|
||||||
|
args.GetReturnValue().Set(result);
|
||||||
|
|
||||||
|
Debug(session, "set local window size to %d", window_size);
|
||||||
|
}
|
||||||
|
|
||||||
// A TypedArray instance is shared between C++ and JS land to contain the
|
// A TypedArray instance is shared between C++ and JS land to contain the
|
||||||
// SETTINGS (either remote or local). RefreshSettings updates the current
|
// SETTINGS (either remote or local). RefreshSettings updates the current
|
||||||
// values established for each of the settings so those can be read in JS land.
|
// values established for each of the settings so those can be read in JS land.
|
||||||
|
|
@ -3105,6 +3124,8 @@ void Initialize(Local<Object> target,
|
||||||
env->SetProtoMethod(session, "request", Http2Session::Request);
|
env->SetProtoMethod(session, "request", Http2Session::Request);
|
||||||
env->SetProtoMethod(session, "setNextStreamID",
|
env->SetProtoMethod(session, "setNextStreamID",
|
||||||
Http2Session::SetNextStreamID);
|
Http2Session::SetNextStreamID);
|
||||||
|
env->SetProtoMethod(session, "setLocalWindowSize",
|
||||||
|
Http2Session::SetLocalWindowSize);
|
||||||
env->SetProtoMethod(session, "updateChunksSent",
|
env->SetProtoMethod(session, "updateChunksSent",
|
||||||
Http2Session::UpdateChunksSent);
|
Http2Session::UpdateChunksSent);
|
||||||
env->SetProtoMethod(session, "refreshState", Http2Session::RefreshState);
|
env->SetProtoMethod(session, "refreshState", Http2Session::RefreshState);
|
||||||
|
|
|
||||||
|
|
@ -700,6 +700,8 @@ class Http2Session : public AsyncWrap,
|
||||||
static void Settings(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void Settings(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void Request(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void Request(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void SetNextStreamID(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void SetNextStreamID(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void SetLocalWindowSize(
|
||||||
|
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void Goaway(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void Goaway(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void UpdateChunksSent(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void UpdateChunksSent(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void RefreshState(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void RefreshState(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
|
@ -1116,6 +1118,7 @@ class Origins {
|
||||||
V(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE) \
|
V(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE) \
|
||||||
V(NGHTTP2_ERR_INVALID_ARGUMENT) \
|
V(NGHTTP2_ERR_INVALID_ARGUMENT) \
|
||||||
V(NGHTTP2_ERR_STREAM_CLOSED) \
|
V(NGHTTP2_ERR_STREAM_CLOSED) \
|
||||||
|
V(NGHTTP2_ERR_NOMEM) \
|
||||||
V(STREAM_OPTION_EMPTY_PAYLOAD) \
|
V(STREAM_OPTION_EMPTY_PAYLOAD) \
|
||||||
V(STREAM_OPTION_GET_TRAILERS)
|
V(STREAM_OPTION_GET_TRAILERS)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ const Countdown = require('../common/countdown');
|
||||||
};
|
};
|
||||||
|
|
||||||
assert.throws(() => client.setNextStreamID(), sessionError);
|
assert.throws(() => client.setNextStreamID(), sessionError);
|
||||||
|
assert.throws(() => client.setLocalWindowSize(), sessionError);
|
||||||
assert.throws(() => client.ping(), sessionError);
|
assert.throws(() => client.ping(), sessionError);
|
||||||
assert.throws(() => client.settings({}), sessionError);
|
assert.throws(() => client.settings({}), sessionError);
|
||||||
assert.throws(() => client.goaway(), sessionError);
|
assert.throws(() => client.goaway(), sessionError);
|
||||||
|
|
@ -88,6 +89,7 @@ const Countdown = require('../common/countdown');
|
||||||
// so that state.destroyed is set to true
|
// so that state.destroyed is set to true
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
assert.throws(() => client.setNextStreamID(), sessionError);
|
assert.throws(() => client.setNextStreamID(), sessionError);
|
||||||
|
assert.throws(() => client.setLocalWindowSize(), sessionError);
|
||||||
assert.throws(() => client.ping(), sessionError);
|
assert.throws(() => client.ping(), sessionError);
|
||||||
assert.throws(() => client.settings({}), sessionError);
|
assert.throws(() => client.settings({}), sessionError);
|
||||||
assert.throws(() => client.goaway(), sessionError);
|
assert.throws(() => client.goaway(), sessionError);
|
||||||
|
|
|
||||||
121
test/parallel/test-http2-client-setLocalWindowSize.js
Normal file
121
test/parallel/test-http2-client-setLocalWindowSize.js
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const http2 = require('http2');
|
||||||
|
|
||||||
|
{
|
||||||
|
const server = http2.createServer();
|
||||||
|
server.on('stream', common.mustNotCall((stream) => {
|
||||||
|
stream.respond();
|
||||||
|
stream.end('ok');
|
||||||
|
}));
|
||||||
|
|
||||||
|
const types = {
|
||||||
|
boolean: true,
|
||||||
|
function: () => {},
|
||||||
|
number: 1,
|
||||||
|
object: {},
|
||||||
|
array: [],
|
||||||
|
null: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
server.listen(0, common.mustCall(() => {
|
||||||
|
const client = http2.connect(`http://localhost:${server.address().port}`);
|
||||||
|
|
||||||
|
client.on('connect', common.mustCall(() => {
|
||||||
|
const outOfRangeNum = 2 ** 32;
|
||||||
|
assert.throws(
|
||||||
|
() => client.setLocalWindowSize(outOfRangeNum),
|
||||||
|
{
|
||||||
|
name: 'RangeError',
|
||||||
|
code: 'ERR_OUT_OF_RANGE',
|
||||||
|
message: 'The value of "windowSize" is out of range.' +
|
||||||
|
' It must be >= 0 && <= 2147483647. Received ' + outOfRangeNum
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Throw if something other than number is passed to setLocalWindowSize
|
||||||
|
Object.entries(types).forEach(([type, value]) => {
|
||||||
|
if (type === 'number') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => client.setLocalWindowSize(value),
|
||||||
|
{
|
||||||
|
name: 'TypeError',
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
message: 'The "windowSize" argument must be of type number.' +
|
||||||
|
common.invalidArgTypeHelper(value)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
client.close();
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const server = http2.createServer();
|
||||||
|
server.on('stream', common.mustNotCall((stream) => {
|
||||||
|
stream.respond();
|
||||||
|
stream.end('ok');
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.listen(0, common.mustCall(() => {
|
||||||
|
const client = http2.connect(`http://localhost:${server.address().port}`);
|
||||||
|
|
||||||
|
client.on('connect', common.mustCall(() => {
|
||||||
|
const windowSize = 2 ** 20;
|
||||||
|
const defaultSetting = http2.getDefaultSettings();
|
||||||
|
client.setLocalWindowSize(windowSize);
|
||||||
|
|
||||||
|
assert.strictEqual(client.state.effectiveLocalWindowSize, windowSize);
|
||||||
|
assert.strictEqual(client.state.localWindowSize, windowSize);
|
||||||
|
assert.strictEqual(
|
||||||
|
client.state.remoteWindowSize,
|
||||||
|
defaultSetting.initialWindowSize
|
||||||
|
);
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
client.close();
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const server = http2.createServer();
|
||||||
|
server.on('stream', common.mustNotCall((stream) => {
|
||||||
|
stream.respond();
|
||||||
|
stream.end('ok');
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.listen(0, common.mustCall(() => {
|
||||||
|
const client = http2.connect(`http://localhost:${server.address().port}`);
|
||||||
|
|
||||||
|
client.on('connect', common.mustCall(() => {
|
||||||
|
const windowSize = 20;
|
||||||
|
const defaultSetting = http2.getDefaultSettings();
|
||||||
|
client.setLocalWindowSize(windowSize);
|
||||||
|
|
||||||
|
assert.strictEqual(client.state.effectiveLocalWindowSize, windowSize);
|
||||||
|
assert.strictEqual(
|
||||||
|
client.state.localWindowSize,
|
||||||
|
defaultSetting.initialWindowSize
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
client.state.remoteWindowSize,
|
||||||
|
defaultSetting.initialWindowSize
|
||||||
|
);
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
client.close();
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
37
test/parallel/test-http2-server-setLocalWindowSize.js
Normal file
37
test/parallel/test-http2-server-setLocalWindowSize.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const http2 = require('http2');
|
||||||
|
|
||||||
|
const server = http2.createServer();
|
||||||
|
server.on('stream', common.mustCall((stream) => {
|
||||||
|
stream.respond();
|
||||||
|
stream.end('ok');
|
||||||
|
}));
|
||||||
|
server.on('session', common.mustCall((session) => {
|
||||||
|
const windowSize = 2 ** 20;
|
||||||
|
const defaultSetting = http2.getDefaultSettings();
|
||||||
|
session.setLocalWindowSize(windowSize);
|
||||||
|
|
||||||
|
assert.strictEqual(session.state.effectiveLocalWindowSize, windowSize);
|
||||||
|
assert.strictEqual(session.state.localWindowSize, windowSize);
|
||||||
|
assert.strictEqual(
|
||||||
|
session.state.remoteWindowSize,
|
||||||
|
defaultSetting.initialWindowSize
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.listen(0, common.mustCall(() => {
|
||||||
|
const client = http2.connect(`http://localhost:${server.address().port}`);
|
||||||
|
|
||||||
|
const req = client.request();
|
||||||
|
req.resume();
|
||||||
|
req.on('close', common.mustCall(() => {
|
||||||
|
client.close();
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
}));
|
||||||
Loading…
Reference in New Issue
Block a user