Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions proxy/http2/Http2ConnectionState.cc
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Http2ConnectionState::rcv_data_frame(const Http2Frame &frame)
}

// Check whether Window Size is acceptable
if (this->server_rwnd() < payload_length) {
if (!this->_server_rwnd_is_shrinking && this->server_rwnd() < payload_length) {
return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_FLOW_CONTROL_ERROR,
"recv data cstate.server_rwnd < payload_length");
}
Expand Down Expand Up @@ -1039,8 +1039,18 @@ Http2ConnectionState::Http2ConnectionState() : stream_list()
void
Http2ConnectionState::init(Http2CommonSession *ssn)
{
session = ssn;
this->_server_rwnd = Http2::initial_window_size;
session = ssn;

if (Http2::initial_window_size < HTTP2_INITIAL_WINDOW_SIZE) {
// There is no HTTP/2 specified way to shrink the connection window size
// other than to receive data and not send WINDOW_UPDATE frames for a
// while.
this->_server_rwnd = HTTP2_INITIAL_WINDOW_SIZE;
this->_server_rwnd_is_shrinking = true;
} else {
this->_server_rwnd = Http2::initial_window_size;
this->_server_rwnd_is_shrinking = false;
}

local_hpack_handle = new HpackHandle(HTTP2_HEADER_TABLE_SIZE);
remote_hpack_handle = new HpackHandle(HTTP2_HEADER_TABLE_SIZE);
Expand Down Expand Up @@ -1424,6 +1434,7 @@ Http2ConnectionState::restart_receiving(Http2Stream *stream)
if (this->server_rwnd() < min_rwnd) {
Http2WindowSize diff_size = initial_rwnd - this->server_rwnd();
this->increment_server_rwnd(diff_size);
this->_server_rwnd_is_shrinking = false;
this->send_window_update_frame(0, diff_size);
}

Expand Down
12 changes: 12 additions & 0 deletions proxy/http2/Http2ConnectionState.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,18 @@ class Http2ConnectionState : public Continuation
ssize_t _client_rwnd = HTTP2_INITIAL_WINDOW_SIZE;
ssize_t _server_rwnd = 0;

/** Whether the session window is in a shrinking state before we send the
* first WINDOW_UPDATE frame.
*
* Unlike HTTP/2 streams, the HTTP/2 session window has no way to initialize
* it to a value lower than 65,535. If the initial value is lower than
* 65,535, the session window will have to shrink while we receive DATA
* frames without incrementing the window via WINDOW_UPDATE frames. Once the
* window gets to the desired size, we start maintaining the window via
* WINDOW_UPDATE frames.
*/
bool _server_rwnd_is_shrinking = false;

std::vector<size_t> _recent_rwnd_increment = {SIZE_MAX, SIZE_MAX, SIZE_MAX, SIZE_MAX, SIZE_MAX};
int _recent_rwnd_increment_index = 0;

Expand Down
237 changes: 237 additions & 0 deletions tests/gold_tests/h2/http2_flow_control.replay.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

meta:
version: "1.0"

# This replay file generates an HTTP/2 session with three streams in order to
# verify that ATS generates the expected SETTINGS and WINDOW_UPDATE frames.

sessions:

- protocol:
- name: http
version: 2
- name: tls
sni: www.example.com
- name: tcp
- name: ip

transactions:

- client-request:
headers:
fields:
- [ :method, GET ]
- [ :scheme, https ]
- [ :authority, www.example.com ]
- [ :path, /zero-request ]
- [ uuid, zero-request ]
- [ X-Request, zero-request ]
- [ Content-Length, 0 ]

proxy-request:
headers:
fields:
- [ X-Request, { value: 'zero-request', as: equal } ]

server-response:
status: 200
reason: OK
headers:
fields:
- [ X-Response, zero-response ]
- [ Content-Length, 28 ]
content:
size: 28

proxy-response:
headers:
fields:
- [ X-Response, { value: 'zero-response', as: equal } ]

- client-request:
delay: 500ms

headers:
fields:
- [ :method, POST ]
- [ :scheme, https ]
- [ :authority, www.example.com ]
- [ :path, /first-request ]
- [ uuid, first-request ]
- [ X-Request, first-request ]
content:
size: 1200

proxy-request:
headers:
fields:
- [ X-Request, { value: 'first-request', as: equal } ]

server-response:
status: 200
reason: OK
headers:
fields:
- [ X-Response, first-response ]
- [ Content-Length, 28 ]
content:
size: 28

proxy-response:
headers:
fields:
- [ X-Response, { value: 'first-response', as: equal } ]

- client-request:
headers:
fields:
- [ :method, POST ]
- [ :scheme, https ]
- [ :authority, www.example.com ]
- [ :path, /second-request ]
- [ uuid, second-request ]
- [ X-Request, second-request ]
content:
size: 1200

proxy-request:
headers:
fields:
- [ X-Request, {value: 'second-request', as: equal } ]

server-response:
status: 200
reason: OK
headers:
fields:
- [ X-Response, second-response ]
- [ Content-Length, 28 ]
content:
size: 28

proxy-response:
headers:
fields:
- [ X-Response, {value: 'second-response', as: equal } ]

- client-request:
headers:
fields:
- [ :method, POST ]
- [ :scheme, https ]
- [ :authority, www.example.com ]
- [ :path, /third-request ]
- [ uuid, third-request ]
- [ X-Request, third-request ]
content:
size: 1200

proxy-request:
headers:
fields:
- [ X-Request, {value: 'third-request', as: equal } ]

server-response:
status: 200
reason: OK
headers:
fields:
- [ X-Response, third-response ]
- [ Content-Length, 28 ]
content:
size: 28

proxy-response:
headers:
fields:
- [ X-Response, {value: 'third-response', as: equal } ]

- client-request:
# Intentionally test a stream after the three other parallel POST
# requests.
delay: 500ms

headers:
fields:
- [ :method, POST ]
- [ :scheme, https ]
- [ :authority, www.example.com ]
- [ :path, /fourth-request ]
- [ uuid, fourth-request ]
- [ X-Request, fourth-request ]
content:
# Send a very large DATA frame so that we exceed the 65,535 window
# size of most of the test runs.
size: 120000

proxy-request:
headers:
fields:
- [ X-Request, {value: 'fourth-request', as: equal } ]

server-response:
status: 200
reason: OK
headers:
fields:
- [ X-Response, fourth-response ]
- [ Content-Length, 28 ]
content:
size: 28

proxy-response:
headers:
fields:
- [ X-Response, {value: 'fourth-response', as: equal } ]

- client-request:
# Give the above request time to process and give us an opportunity to
# receive any other WINDOW_UPDATE frames.
delay: 500ms

headers:
fields:
- [ :method, POST ]
- [ :scheme, https ]
- [ :authority, www.example.com ]
- [ :path, /fifth-request ]
- [ uuid, fifth-request ]
- [ X-Request, fifth-request ]
content:
size: 10000

proxy-request:
headers:
fields:
- [ X-Request, {value: 'fifth-request', as: equal } ]

server-response:
status: 200
reason: OK
headers:
fields:
- [ X-Response, fifth-response ]
- [ Content-Length, 28 ]
content:
size: 28

proxy-response:
headers:
fields:
- [ X-Response, {value: 'fifth-response', as: equal } ]

Loading