Skip to content

Commit 9c6be3c

Browse files
zhangyongshengcodebytere
zhangyongsheng
authored andcommittedNov 22, 2020
http2: allow setting the local window size of a session
PR-URL: #35978 Fixes: #31084 Refs: #26962 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Ricky Zhou <[email protected]>
1 parent 0b40568 commit 9c6be3c

9 files changed

+237
-5
lines changed
 

‎doc/api/errors.md

+5
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,11 @@ reached.
12261226
An attempt was made to initiate a new push stream from within a push stream.
12271227
Nested push streams are not permitted.
12281228

1229+
<a id="ERR_HTTP2_NO_MEM"></a>
1230+
### `ERR_HTTP2_NO_MEM`
1231+
1232+
Out of memory when using the `http2session.setLocalWindowSize(windowSize)` API.
1233+
12291234
<a id="ERR_HTTP2_NO_SOCKET_MANIPULATION"></a>
12301235
### `ERR_HTTP2_NO_SOCKET_MANIPULATION`
12311236

‎doc/api/http2.md

+23
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,29 @@ added: v8.4.0
519519
A prototype-less object describing the current remote settings of this
520520
`Http2Session`. The remote settings are set by the *connected* HTTP/2 peer.
521521

522+
#### `http2session.setLocalWindowSize(windowSize)`
523+
<!-- YAML
524+
added: REPLACEME
525+
-->
526+
527+
* `windowSize` {number}
528+
529+
Sets the local endpoint's window size.
530+
The `windowSize` is the total window size to set, not
531+
the delta.
532+
533+
```js
534+
const http2 = require('http2');
535+
536+
const server = http2.createServer();
537+
const expectedWindowSize = 2 ** 20;
538+
server.on('connect', (session) => {
539+
540+
// Set local window size to be 2 ** 20
541+
session.setLocalWindowSize(expectedWindowSize);
542+
});
543+
```
544+
522545
#### `http2session.setTimeout(msecs, callback)`
523546
<!-- YAML
524547
added: v8.4.0

‎lib/internal/errors.js

+1
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,7 @@ E('ERR_HTTP2_MAX_PENDING_SETTINGS_ACK',
904904
'Maximum number of pending settings acknowledgements', Error);
905905
E('ERR_HTTP2_NESTED_PUSH',
906906
'A push stream cannot initiate another push stream.', Error);
907+
E('ERR_HTTP2_NO_MEM', 'Out of memory', Error);
907908
E('ERR_HTTP2_NO_SOCKET_MANIPULATION',
908909
'HTTP/2 sockets should not be directly manipulated (e.g. read and written)',
909910
Error);

‎lib/internal/http2/core.js

+24-5
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ const {
7070
ERR_HTTP2_INVALID_STREAM,
7171
ERR_HTTP2_MAX_PENDING_SETTINGS_ACK,
7272
ERR_HTTP2_NESTED_PUSH,
73+
ERR_HTTP2_NO_MEM,
7374
ERR_HTTP2_NO_SOCKET_MANIPULATION,
7475
ERR_HTTP2_ORIGIN_LENGTH,
7576
ERR_HTTP2_OUT_OF_STREAMS,
@@ -101,11 +102,13 @@ const {
101102
},
102103
hideStackFrames
103104
} = require('internal/errors');
104-
const { validateInteger,
105-
validateNumber,
106-
validateString,
107-
validateUint32,
108-
isUint32,
105+
const {
106+
isUint32,
107+
validateInt32,
108+
validateInteger,
109+
validateNumber,
110+
validateString,
111+
validateUint32,
109112
} = require('internal/validators');
110113
const fsPromisesInternal = require('internal/fs/promises');
111114
const { utcDate } = require('internal/http');
@@ -252,6 +255,7 @@ const {
252255
NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE,
253256
NGHTTP2_ERR_INVALID_ARGUMENT,
254257
NGHTTP2_ERR_STREAM_CLOSED,
258+
NGHTTP2_ERR_NOMEM,
255259

256260
HTTP2_HEADER_AUTHORITY,
257261
HTTP2_HEADER_DATE,
@@ -1254,6 +1258,21 @@ class Http2Session extends EventEmitter {
12541258
this[kHandle].setNextStreamID(id);
12551259
}
12561260

1261+
// Sets the local window size (local endpoints's window size)
1262+
// Returns 0 if sucess or throw an exception if NGHTTP2_ERR_NOMEM
1263+
// if the window allocation fails
1264+
setLocalWindowSize(windowSize) {
1265+
if (this.destroyed)
1266+
throw new ERR_HTTP2_INVALID_SESSION();
1267+
1268+
validateInt32(windowSize, 'windowSize', 0);
1269+
const ret = this[kHandle].setLocalWindowSize(windowSize);
1270+
1271+
if (ret === NGHTTP2_ERR_NOMEM) {
1272+
this.destroy(new ERR_HTTP2_NO_MEM());
1273+
}
1274+
}
1275+
12571276
// If ping is called while we are still connecting, or after close() has
12581277
// been called, the ping callback will be invoked immediately will a ping
12591278
// cancelled error and a duration of 0.0.

‎src/node_http2.cc

+21
Original file line numberDiff line numberDiff line change
@@ -2416,6 +2416,25 @@ void Http2Session::SetNextStreamID(const FunctionCallbackInfo<Value>& args) {
24162416
Debug(session, "set next stream id to %d", id);
24172417
}
24182418

2419+
// Set local window size (local endpoints's window size) to the given
2420+
// window_size for the stream denoted by 0.
2421+
// This function returns 0 if it succeeds, or one of a negative codes
2422+
void Http2Session::SetLocalWindowSize(
2423+
const FunctionCallbackInfo<Value>& args) {
2424+
Environment* env = Environment::GetCurrent(args);
2425+
Http2Session* session;
2426+
ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2427+
2428+
int32_t window_size = args[0]->Int32Value(env->context()).ToChecked();
2429+
2430+
int result = nghttp2_session_set_local_window_size(
2431+
session->session(), NGHTTP2_FLAG_NONE, 0, window_size);
2432+
2433+
args.GetReturnValue().Set(result);
2434+
2435+
Debug(session, "set local window size to %d", window_size);
2436+
}
2437+
24192438
// A TypedArray instance is shared between C++ and JS land to contain the
24202439
// SETTINGS (either remote or local). RefreshSettings updates the current
24212440
// values established for each of the settings so those can be read in JS land.
@@ -3088,6 +3107,8 @@ void Initialize(Local<Object> target,
30883107
env->SetProtoMethod(session, "request", Http2Session::Request);
30893108
env->SetProtoMethod(session, "setNextStreamID",
30903109
Http2Session::SetNextStreamID);
3110+
env->SetProtoMethod(session, "setLocalWindowSize",
3111+
Http2Session::SetLocalWindowSize);
30913112
env->SetProtoMethod(session, "updateChunksSent",
30923113
Http2Session::UpdateChunksSent);
30933114
env->SetProtoMethod(session, "refreshState", Http2Session::RefreshState);

‎src/node_http2.h

+3
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,8 @@ class Http2Session : public AsyncWrap,
699699
static void Settings(const v8::FunctionCallbackInfo<v8::Value>& args);
700700
static void Request(const v8::FunctionCallbackInfo<v8::Value>& args);
701701
static void SetNextStreamID(const v8::FunctionCallbackInfo<v8::Value>& args);
702+
static void SetLocalWindowSize(
703+
const v8::FunctionCallbackInfo<v8::Value>& args);
702704
static void Goaway(const v8::FunctionCallbackInfo<v8::Value>& args);
703705
static void UpdateChunksSent(const v8::FunctionCallbackInfo<v8::Value>& args);
704706
static void RefreshState(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -1115,6 +1117,7 @@ class Origins {
11151117
V(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE) \
11161118
V(NGHTTP2_ERR_INVALID_ARGUMENT) \
11171119
V(NGHTTP2_ERR_STREAM_CLOSED) \
1120+
V(NGHTTP2_ERR_NOMEM) \
11181121
V(STREAM_OPTION_EMPTY_PAYLOAD) \
11191122
V(STREAM_OPTION_GET_TRAILERS)
11201123

‎test/parallel/test-http2-client-destroy.js

+2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ const Countdown = require('../common/countdown');
7777
};
7878

7979
assert.throws(() => client.setNextStreamID(), sessionError);
80+
assert.throws(() => client.setLocalWindowSize(), sessionError);
8081
assert.throws(() => client.ping(), sessionError);
8182
assert.throws(() => client.settings({}), sessionError);
8283
assert.throws(() => client.goaway(), sessionError);
@@ -87,6 +88,7 @@ const Countdown = require('../common/countdown');
8788
// so that state.destroyed is set to true
8889
setImmediate(() => {
8990
assert.throws(() => client.setNextStreamID(), sessionError);
91+
assert.throws(() => client.setLocalWindowSize(), sessionError);
9092
assert.throws(() => client.ping(), sessionError);
9193
assert.throws(() => client.settings({}), sessionError);
9294
assert.throws(() => client.goaway(), sessionError);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
const assert = require('assert');
8+
const http2 = require('http2');
9+
10+
{
11+
const server = http2.createServer();
12+
server.on('stream', common.mustNotCall((stream) => {
13+
stream.respond();
14+
stream.end('ok');
15+
}));
16+
17+
const types = {
18+
boolean: true,
19+
function: () => {},
20+
number: 1,
21+
object: {},
22+
array: [],
23+
null: null,
24+
};
25+
26+
server.listen(0, common.mustCall(() => {
27+
const client = http2.connect(`http://localhost:${server.address().port}`);
28+
29+
client.on('connect', common.mustCall(() => {
30+
const outOfRangeNum = 2 ** 32;
31+
assert.throws(
32+
() => client.setLocalWindowSize(outOfRangeNum),
33+
{
34+
name: 'RangeError',
35+
code: 'ERR_OUT_OF_RANGE',
36+
message: 'The value of "windowSize" is out of range.' +
37+
' It must be >= 0 && <= 2147483647. Received ' + outOfRangeNum
38+
}
39+
);
40+
41+
// Throw if something other than number is passed to setLocalWindowSize
42+
Object.entries(types).forEach(([type, value]) => {
43+
if (type === 'number') {
44+
return;
45+
}
46+
47+
assert.throws(
48+
() => client.setLocalWindowSize(value),
49+
{
50+
name: 'TypeError',
51+
code: 'ERR_INVALID_ARG_TYPE',
52+
message: 'The "windowSize" argument must be of type number.' +
53+
common.invalidArgTypeHelper(value)
54+
}
55+
);
56+
});
57+
58+
server.close();
59+
client.close();
60+
}));
61+
}));
62+
}
63+
64+
{
65+
const server = http2.createServer();
66+
server.on('stream', common.mustNotCall((stream) => {
67+
stream.respond();
68+
stream.end('ok');
69+
}));
70+
71+
server.listen(0, common.mustCall(() => {
72+
const client = http2.connect(`http://localhost:${server.address().port}`);
73+
74+
client.on('connect', common.mustCall(() => {
75+
const windowSize = 2 ** 20;
76+
const defaultSetting = http2.getDefaultSettings();
77+
client.setLocalWindowSize(windowSize);
78+
79+
assert.strictEqual(client.state.effectiveLocalWindowSize, windowSize);
80+
assert.strictEqual(client.state.localWindowSize, windowSize);
81+
assert.strictEqual(
82+
client.state.remoteWindowSize,
83+
defaultSetting.initialWindowSize
84+
);
85+
86+
server.close();
87+
client.close();
88+
}));
89+
}));
90+
}
91+
92+
{
93+
const server = http2.createServer();
94+
server.on('stream', common.mustNotCall((stream) => {
95+
stream.respond();
96+
stream.end('ok');
97+
}));
98+
99+
server.listen(0, common.mustCall(() => {
100+
const client = http2.connect(`http://localhost:${server.address().port}`);
101+
102+
client.on('connect', common.mustCall(() => {
103+
const windowSize = 20;
104+
const defaultSetting = http2.getDefaultSettings();
105+
client.setLocalWindowSize(windowSize);
106+
107+
assert.strictEqual(client.state.effectiveLocalWindowSize, windowSize);
108+
assert.strictEqual(
109+
client.state.localWindowSize,
110+
defaultSetting.initialWindowSize
111+
);
112+
assert.strictEqual(
113+
client.state.remoteWindowSize,
114+
defaultSetting.initialWindowSize
115+
);
116+
117+
server.close();
118+
client.close();
119+
}));
120+
}));
121+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
const assert = require('assert');
8+
const http2 = require('http2');
9+
10+
const server = http2.createServer();
11+
server.on('stream', common.mustCall((stream) => {
12+
stream.respond();
13+
stream.end('ok');
14+
}));
15+
server.on('session', common.mustCall((session) => {
16+
const windowSize = 2 ** 20;
17+
const defaultSetting = http2.getDefaultSettings();
18+
session.setLocalWindowSize(windowSize);
19+
20+
assert.strictEqual(session.state.effectiveLocalWindowSize, windowSize);
21+
assert.strictEqual(session.state.localWindowSize, windowSize);
22+
assert.strictEqual(
23+
session.state.remoteWindowSize,
24+
defaultSetting.initialWindowSize
25+
);
26+
}));
27+
28+
server.listen(0, common.mustCall(() => {
29+
const client = http2.connect(`http://localhost:${server.address().port}`);
30+
31+
const req = client.request();
32+
req.resume();
33+
req.on('close', common.mustCall(() => {
34+
client.close();
35+
server.close();
36+
}));
37+
}));

0 commit comments

Comments
 (0)
Please sign in to comment.