Skip to content

Commit 9c6be3c

Browse files
zhangyongshengcodebytere
authored andcommitted
http2: allow setting the local window size of a session
PR-URL: #35978Fixes: #31084 Refs: #26962 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Ricky Zhou <0x19951125@gmail.com>
1 parent 0b40568 commit 9c6be3c

9 files changed

Lines changed: 237 additions & 5 deletions

File tree

‎doc/api/errors.md‎

Lines changed: 5 additions & 0 deletions
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+
<aid="ERR_HTTP2_NO_MEM"></a>
1230+
### `ERR_HTTP2_NO_MEM`
1231+
1232+
Out of memory when using the `http2session.setLocalWindowSize(windowSize)` API.
1233+
12291234
<aid="ERR_HTTP2_NO_SOCKET_MANIPULATION"></a>
12301235
### `ERR_HTTP2_NO_SOCKET_MANIPULATION`
12311236

‎doc/api/http2.md‎

Lines changed: 23 additions & 0 deletions
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+
consthttp2=require('http2');
535+
536+
constserver=http2.createServer();
537+
constexpectedWindowSize=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‎

Lines changed: 1 addition & 0 deletions
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‎

Lines changed: 24 additions & 5 deletions
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
constfsPromisesInternal=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+
thrownewERR_HTTP2_INVALID_SESSION();
1267+
1268+
validateInt32(windowSize,'windowSize',0);
1269+
constret=this[kHandle].setLocalWindowSize(windowSize);
1270+
1271+
if(ret===NGHTTP2_ERR_NOMEM){
1272+
this.destroy(newERR_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‎

Lines changed: 21 additions & 0 deletions
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+
voidHttp2Session::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‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,8 @@ class Http2Session : public AsyncWrap,
699699
staticvoidSettings(const v8::FunctionCallbackInfo<v8::Value>& args);
700700
staticvoidRequest(const v8::FunctionCallbackInfo<v8::Value>& args);
701701
staticvoidSetNextStreamID(const v8::FunctionCallbackInfo<v8::Value>& args);
702+
staticvoidSetLocalWindowSize(
703+
const v8::FunctionCallbackInfo<v8::Value>& args);
702704
staticvoidGoaway(const v8::FunctionCallbackInfo<v8::Value>& args);
703705
staticvoidUpdateChunksSent(const v8::FunctionCallbackInfo<v8::Value>& args);
704706
staticvoidRefreshState(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‎

Lines changed: 2 additions & 0 deletions
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);
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
if(!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
constassert=require('assert');
8+
consthttp2=require('http2');
9+
10+
{
11+
constserver=http2.createServer();
12+
server.on('stream',common.mustNotCall((stream)=>{
13+
stream.respond();
14+
stream.end('ok');
15+
}));
16+
17+
consttypes={
18+
boolean: true,
19+
function: ()=>{},
20+
number: 1,
21+
object: {},
22+
array: [],
23+
null: null,
24+
};
25+
26+
server.listen(0,common.mustCall(()=>{
27+
constclient=http2.connect(`http://localhost:${server.address().port}`);
28+
29+
client.on('connect',common.mustCall(()=>{
30+
constoutOfRangeNum=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+
constserver=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+
constclient=http2.connect(`http://localhost:${server.address().port}`);
73+
74+
client.on('connect',common.mustCall(()=>{
75+
constwindowSize=2**20;
76+
constdefaultSetting=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+
constserver=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+
constclient=http2.connect(`http://localhost:${server.address().port}`);
101+
102+
client.on('connect',common.mustCall(()=>{
103+
constwindowSize=20;
104+
constdefaultSetting=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+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
if(!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
constassert=require('assert');
8+
consthttp2=require('http2');
9+
10+
constserver=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+
constwindowSize=2**20;
17+
constdefaultSetting=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+
constclient=http2.connect(`http://localhost:${server.address().port}`);
30+
31+
constreq=client.request();
32+
req.resume();
33+
req.on('close',common.mustCall(()=>{
34+
client.close();
35+
server.close();
36+
}));
37+
}));

0 commit comments

Comments
 (0)