From c047c490749c7d14cac042e2b470856c25c60168 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Thu, 21 Feb 2019 15:16:54 -0800 Subject: [PATCH] Ensure that MAX_FRAME_SIZE is respected when sending responses to clients --- tornado_http2/connection.py | 1 - tornado_http2/stream.py | 4 +++- tornado_http2/test/benchmark.py | 3 ++- tornado_http2/test/encoding_test.py | 8 +++++++- tornado_http2/test/runtests.py | 1 + tornado_http2/test/server_test.py | 21 ++++++++++++++++++--- 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/tornado_http2/connection.py b/tornado_http2/connection.py index f37f9e3..7bd651d 100644 --- a/tornado_http2/connection.py +++ b/tornado_http2/connection.py @@ -35,7 +35,6 @@ def __init__(self, stream, is_client, params=None, context=None): self._initial_settings_written = Future() self._serving_future = None self._settings = {} - self.streams = {} self.next_stream_id = 1 if is_client else 2 self.hpack_decoder = HpackDecoder( diff --git a/tornado_http2/stream.py b/tornado_http2/stream.py index 9a1f237..15e36d2 100644 --- a/tornado_http2/stream.py +++ b/tornado_http2/stream.py @@ -323,7 +323,9 @@ def _write_chunk(self, chunk, callback=None): if chunk: yield self.write_lock.acquire() while chunk: - allowance = yield self.window.consume(len(chunk)) + bytes_to_write = min(len(chunk), self.conn.setting( + constants.Setting.MAX_FRAME_SIZE)) + allowance = yield self.window.consume(bytes_to_write) yield self.conn._write_frame( Frame(constants.FrameType.DATA, 0, diff --git a/tornado_http2/test/benchmark.py b/tornado_http2/test/benchmark.py index 35f83f7..787e4ad 100644 --- a/tornado_http2/test/benchmark.py +++ b/tornado_http2/test/benchmark.py @@ -46,7 +46,7 @@ def benchmark(version): def print_result(label, elapsed): print('HTTP/%s: %d requests in %0.3fs: %f QPS' % (label, options.n, elapsed, - options.n / elapsed)) + options.n / elapsed)) @gen.coroutine @@ -58,5 +58,6 @@ def main(): elapsed = yield benchmark(int(version)) print_result(version, elapsed) + if __name__ == '__main__': IOLoop.current().run_sync(main) diff --git a/tornado_http2/test/encoding_test.py b/tornado_http2/test/encoding_test.py index ac1993d..6358730 100644 --- a/tornado_http2/test/encoding_test.py +++ b/tornado_http2/test/encoding_test.py @@ -2,6 +2,7 @@ from tornado_http2.encoding import BitEncoder, BitDecoder, EODError + class TestData(object): def __init__(self, *args): self.args = args @@ -14,6 +15,7 @@ def decode(self, test, decoder): for arg in self.args: test.assertEqual(self.decode_value(decoder), arg) + class Bits(TestData): def encode_value(self, encoder, arg): encoder.write_bit(arg) @@ -21,6 +23,7 @@ def encode_value(self, encoder, arg): def decode_value(self, decoder): return decoder.read_bit() + class HpackInt(TestData): def encode_value(self, encoder, arg): encoder.write_hpack_int(arg) @@ -28,6 +31,7 @@ def encode_value(self, encoder, arg): def decode_value(self, decoder): return decoder.read_hpack_int() + class HuffChar(TestData): def __init__(self, data): # convert strings to a sequence of bytes @@ -39,6 +43,7 @@ def encode_value(self, encoder, arg): def decode_value(self, decoder): return decoder.read_huffman_char(None) + test_data = [ ('1-bit', [Bits(1)], [0b10000000], False), ('5-bits', [Bits(1, 0, 1, 1, 0)], [0b10110000], False), @@ -78,7 +83,8 @@ def decode_value(self, decoder): # Individual huffman-encoded characters ('huff1', [HuffChar(b'a')], [0b00011000], False), ('huff2', [HuffChar(b'Hi')], [0b11000110, 0b01100000], False), - ] +] + class BitEncodingTest(unittest.TestCase): def test_bit_encoder(self): diff --git a/tornado_http2/test/runtests.py b/tornado_http2/test/runtests.py index 6426729..c48bac2 100644 --- a/tornado_http2/test/runtests.py +++ b/tornado_http2/test/runtests.py @@ -29,5 +29,6 @@ def configure_httpclient(): logging.getLogger("tornado.access").setLevel(logging.CRITICAL) tornado.testing.main() + if __name__ == '__main__': main() diff --git a/tornado_http2/test/server_test.py b/tornado_http2/test/server_test.py index e0d0dab..2a16d2b 100644 --- a/tornado_http2/test/server_test.py +++ b/tornado_http2/test/server_test.py @@ -15,16 +15,24 @@ def get(self): class ServerTest(AsyncHTTP2TestCase): def get_app(self): - class LargeResponseHandler(RequestHandler): + class LargeChunkedResponseHandler(RequestHandler): @gen.coroutine def get(self): for i in range(200): self.write(b'a' * 1024) yield self.flush() + class ExtraLargeResponseHandler(RequestHandler): + """Send the data all at once""" + + def get(self): + self.write(b'a' * 1024 * 200) + self.flush() + return Application([ ('/hello', HelloHandler), - ('/large', LargeResponseHandler), + ('/large', LargeChunkedResponseHandler), + ('/extralarge', ExtraLargeResponseHandler), ]) def test_hello(self): @@ -32,13 +40,20 @@ def test_hello(self): resp.rethrow() self.assertEqual(resp.body, b'Hello HTTP/2.0') - def test_large_response(self): + def test_large_response_chunked(self): # This mainly tests that WINDOW_UPDATE frames are sent as needed, # since this response exceeds the default 64KB window. resp = self.fetch('/large') resp.rethrow() self.assertEqual(len(resp.body), 200 * 1024) + def test_large_response(self): + # This mainly tests that the server will respect the MAX_FRAME_SIZE + # when sending data on the connection. + resp = self.fetch('/extralarge') + resp.rethrow() + self.assertEqual(len(resp.body), 200 * 1024) + class HTTPSTest(AsyncHTTP2TestCase): def get_app(self):