diff --git a/CHANGES/2109.removal b/CHANGES/2109.removal new file mode 100644 index 00000000000..c37fd6569c9 --- /dev/null +++ b/CHANGES/2109.removal @@ -0,0 +1 @@ +Simplify HTTP pipelining implementation diff --git a/aiohttp/http_writer.py b/aiohttp/http_writer.py index 1ca0759910f..0f35a2d2868 100644 --- a/aiohttp/http_writer.py +++ b/aiohttp/http_writer.py @@ -6,7 +6,7 @@ from contextlib import suppress from .abc import AbstractPayloadWriter -from .helpers import noop, set_result +from .helpers import noop __all__ = ('PayloadWriter', 'HttpVersion', 'HttpVersion10', 'HttpVersion11', @@ -34,34 +34,8 @@ def __init__(self, protocol, transport, loop): self._tcp_cork = False self._socket = transport.get_extra_info('socket') self._waiters = [] - self.available = True self.transport = transport - def acquire(self, writer): - if self.available: - self.available = False - writer.set_transport(self.transport) - else: - self._waiters.append(writer) - - def release(self): - if self._waiters: - self.available = False - writer = self._waiters.pop(0) - writer.set_transport(self.transport) - else: - self.available = True - - def replace(self, writer, factory): - try: - idx = self._waiters.index(writer) - writer = factory(self, self._loop, False) - self._waiters[idx] = writer - return writer - except ValueError: - self.available = True - return factory(self, self._loop) - @property def tcp_nodelay(self): return self._tcp_nodelay @@ -122,10 +96,9 @@ async def drain(self): class PayloadWriter(AbstractPayloadWriter): - def __init__(self, stream, loop, acquire=True): + def __init__(self, stream, loop): self._stream = stream self._transport = None - self._buffer = [] self.loop = loop self.length = None @@ -136,32 +109,9 @@ def __init__(self, stream, loop, acquire=True): self._eof = False self._compress = None self._drain_waiter = None - - if self._stream.available: - self._transport = self._stream.transport - self._buffer = None - self._stream.available = False - elif acquire: - self._stream.acquire(self) - - def set_transport(self, transport): - self._transport = transport - - if self._buffer is not None: - for chunk in self._buffer: - transport.write(chunk) - self._buffer = None - - if self._drain_waiter is not None: - waiter, self._drain_waiter = self._drain_waiter, None - set_result(waiter, None) + self._transport = self._stream.transport async def get_transport(self): - if self._transport is None: - if self._drain_waiter is None: - self._drain_waiter = self.loop.create_future() - await self._drain_waiter - return self._transport def enable_chunking(self): @@ -176,12 +126,7 @@ def _write(self, chunk): size = len(chunk) self.buffer_size += size self.output_size += size - - # see set_transport: exactly one of _buffer or _transport is None - if self._transport is not None: - self._transport.write(chunk) - else: - self._buffer.append(chunk) + self._transport.write(chunk) def write(self, chunk, *, drain=True, LIMIT=64*1024): """Writes chunk of data to a stream. @@ -253,11 +198,6 @@ async def write_eof(self, chunk=b''): self._eof = True self._transport = None - self._stream.release() async def drain(self): - if self._transport is not None: - await self._stream.drain() - else: - # wait for transport - await self.get_transport() + await self._stream.drain() diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index f8777a6bcd0..9eebae3fbb7 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -111,9 +111,12 @@ async def _sendfile_system(self, request, fobj, count): transport.get_extra_info("socket") is None): writer = await self._sendfile_fallback(request, fobj, count) else: - writer = request._protocol.writer.replace( - request._payload_writer, SendfilePayloadWriter) + writer = SendfilePayloadWriter( + request._protocol.writer, + request.loop + ) request._payload_writer = writer + await super().prepare(request) await writer.sendfile(fobj, count) diff --git a/aiohttp/web_protocol.py b/aiohttp/web_protocol.py index cc3a41c5ef6..76c944150cb 100644 --- a/aiohttp/web_protocol.py +++ b/aiohttp/web_protocol.py @@ -92,8 +92,7 @@ def __init__(self, manager, *, loop=None, max_line_size=8190, max_headers=32768, max_field_size=8190, - lingering_time=10.0, - max_concurrent_handlers=1): + lingering_time=10.0): super().__init__(loop=loop) @@ -112,10 +111,9 @@ def __init__(self, manager, *, loop=None, self._messages = deque() self._message_tail = b'' - self._waiters = deque() + self._waiter = None self._error_handler = None - self._request_handlers = [] - self._max_concurrent_handlers = max_concurrent_handlers + self._task_handler = self._loop.create_task(self.start()) self._upgrade = False self._payload_parser = None @@ -142,17 +140,8 @@ def __init__(self, manager, *, loop=None, self._force_close = False def __repr__(self): - self._request = None - if self._request is None: - meth = 'none' - path = 'none' - else: - meth = 'none' - path = 'none' - # meth = self._request.method - # path = self._request.rel_url.raw_path - return "<{} {}:{} {}>".format( - self.__class__.__name__, meth, path, + return "<{} {}>".format( + self.__class__.__name__, 'connected' if self.transport is not None else 'disconnected') @property @@ -168,9 +157,8 @@ async def shutdown(self, timeout=15.0): if self._keepalive_handle is not None: self._keepalive_handle.cancel() - # cancel waiters - for waiter in self._waiters: - waiter.cancel() + if self._waiter: + self._waiter.cancel() # wait for handlers with suppress(asyncio.CancelledError, asyncio.TimeoutError): @@ -178,28 +166,17 @@ async def shutdown(self, timeout=15.0): if self._error_handler and not self._error_handler.done(): await self._error_handler - while True: - h = None - for handler in self._request_handlers: - if not handler.done(): - h = handler - break - if h: - await h - else: - break + if self._task_handler and not self._task_handler.done(): + await self._task_handler - # force-close non-idle handlers - for handler in self._request_handlers: - handler.cancel() + # force-close non-idle handler + if self._task_handler: + self._task_handler.cancel() if self.transport is not None: self.transport.close() self.transport = None - if self._request_handlers: - self._request_handlers.clear() - def connection_made(self, transport): super().connection_made(transport) @@ -228,13 +205,13 @@ def connection_lost(self, exc): if self._keepalive_handle is not None: self._keepalive_handle.cancel() - for handler in self._request_handlers: - handler.cancel() + if self._task_handler: + self._task_handler.cancel() if self._error_handler is not None: self._error_handler.cancel() - self._request_handlers = () + self._task_handler = None if self._payload_parser is not None: self._payload_parser.feed_eof() @@ -262,34 +239,25 @@ def data_received(self, data): messages, upgraded, tail = self._request_parser.feed_data(data) except HttpProcessingError as exc: # something happened during parsing - self.close() self._error_handler = self._loop.create_task( self.handle_parse_error( PayloadWriter(self.writer, self._loop), 400, exc, exc.message)) + self.close() except Exception as exc: # 500: internal error - self.close() self._error_handler = self._loop.create_task( self.handle_parse_error( PayloadWriter(self.writer, self._loop), 500, exc)) + self.close() else: for (msg, payload) in messages: self._request_count += 1 + self._messages.append((msg, payload)) - if self._waiters: - waiter = self._waiters.popleft() - waiter.set_result((msg, payload)) - elif self._max_concurrent_handlers: - self._max_concurrent_handlers -= 1 - data = [] - handler = self._loop.create_task( - self.start(msg, payload, data)) - data.append(handler) - self._request_handlers.append(handler) - else: - self._messages.append((msg, payload)) + if self._waiter: + self._waiter.set_result(None) self._upgraded = upgraded if upgraded and tail: @@ -316,14 +284,14 @@ def close(self): """Stop accepting new pipelinig messages and close connection when handlers done processing messages""" self._close = True - for waiter in self._waiters: - waiter.cancel() + if self._waiter: + self._waiter.cancel() def force_close(self, send_last_heartbeat=False): """Force close connection""" self._force_close = True - for waiter in self._waiters: - waiter.cancel() + if self._waiter: + self._waiter.cancel() if self.transport is not None: if send_last_heartbeat: self.transport.write(b"\r\n") @@ -347,8 +315,8 @@ def _process_keepalive(self): next = self._keepalive_time + self._keepalive_timeout - # all handlers in idle state - if len(self._request_handlers) == len(self._waiters): + # handler in idle state + if self._waiter: if self._loop.time() > next: self.force_close(send_last_heartbeat=True) return @@ -372,8 +340,8 @@ def resume_reading(self): pass self._reading_paused = False - async def start(self, message, payload, handler): - """Start processing of incoming requests. + async def start(self): + """Process incoming request. It reads request line, request headers and request payload, then calls handle_request() method. Subclass has to override @@ -382,11 +350,23 @@ async def start(self, message, payload, handler): keep_alive(True) specified. """ loop = self._loop - handler = handler[0] + handler = self._task_handler manager = self._manager keepalive_timeout = self._keepalive_timeout while not self._force_close: + if not self._messages: + try: + # wait for next request + self._waiter = loop.create_future() + await self._waiter + except asyncio.CancelledError: + break + finally: + self._waiter = None + + message, payload = self._messages.popleft() + if self.access_log: now = loop.time() @@ -466,36 +446,23 @@ async def start(self, message, payload, handler): if self.transport is None: self.log_debug('Ignored premature client disconnection.') elif not self._force_close: - if self._messages: - message, payload = self._messages.popleft() + if self._keepalive and not self._close: + # start keep-alive timer + if keepalive_timeout is not None: + now = self._loop.time() + self._keepalive_time = now + if self._keepalive_handle is None: + self._keepalive_handle = loop.call_at( + now + keepalive_timeout, + self._process_keepalive) else: - if self._keepalive and not self._close: - # start keep-alive timer - if keepalive_timeout is not None: - now = self._loop.time() - self._keepalive_time = now - if self._keepalive_handle is None: - self._keepalive_handle = loop.call_at( - now + keepalive_timeout, - self._process_keepalive) - - # wait for next request - waiter = loop.create_future() - self._waiters.append(waiter) - try: - message, payload = await waiter - except asyncio.CancelledError: - # shutdown process - break - else: - break + break # remove handler, close transport if no handlers left if not self._force_close: - self._request_handlers.remove(handler) - if not self._request_handlers: - if self.transport is not None: - self.transport.close() + self._task_handler = None + if self.transport is not None and self._error_handler is None: + self.transport.close() def handle_error(self, request, status=500, exc=None, message=None): """Handle errors. @@ -537,3 +504,8 @@ async def handle_parse_error(self, writer, status, exc=None, message=None): resp = self.handle_error(request, status, exc, message) await resp.prepare(request) await resp.write_eof() + + if self.transport is not None: + self.transport.close() + + self._error_handler = None diff --git a/docs/client_reference.rst b/docs/client_reference.rst index e82f5e6eb4c..8c1fecc15f9 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -101,7 +101,7 @@ The client session supports the context manager protocol for self closing. proxy mode. If no cookie processing is needed, a - :class:`aiohttp.helpers.DummyCookieJar` instance can be + :class:`aiohttp.DummyCookieJar` instance can be provided. :param callable json_serialize: Json *serializer* callable. diff --git a/tests/test_http_stream_writer.py b/tests/test_http_stream_writer.py index bf5ef8be280..b4fdb2288a5 100644 --- a/tests/test_http_stream_writer.py +++ b/tests/test_http_stream_writer.py @@ -3,7 +3,7 @@ import pytest -from aiohttp.http_writer import CORK, PayloadWriter, StreamWriter +from aiohttp.http_writer import CORK, StreamWriter has_ipv6 = socket.has_ipv6 @@ -255,80 +255,3 @@ def test_set_enabling_nodelay_disables_cork(loop): assert s.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) assert not writer.tcp_cork assert not s.getsockopt(socket.IPPROTO_TCP, CORK) - - -# payload writers management - -def test_acquire(loop): - transport = mock.Mock() - stream = StreamWriter(mock.Mock(), transport, loop) - assert stream.available - - payload = PayloadWriter(stream, loop) - assert not stream.available - assert payload._transport is transport - - payload2 = PayloadWriter(stream, loop) - assert payload2._transport is None - assert payload2 in stream._waiters - - -def test_acquire2(loop): - transport = mock.Mock() - stream = StreamWriter(mock.Mock(), transport, loop) - - payload = PayloadWriter(stream, loop) - stream.release() - assert stream.available - - stream.acquire(payload) - assert not stream.available - assert payload._transport is transport - - -def test_release(loop): - transport = mock.Mock() - stream = StreamWriter(mock.Mock(), transport, loop) - stream.available = False - - writer = mock.Mock() - - stream.acquire(writer) - assert not stream.available - assert not writer.set_transport.called - - stream.release() - assert not stream.available - writer.set_transport.assert_called_with(transport) - - stream.release() - assert stream.available - - -def test_replace(loop): - transport = mock.Mock() - stream = StreamWriter(mock.Mock(), transport, loop) - stream.available = False - - payload = PayloadWriter(stream, loop) - assert payload._transport is None - assert payload in stream._waiters - - payload2 = stream.replace(payload, PayloadWriter) - assert payload2._transport is None - assert payload2 in stream._waiters - assert payload not in stream._waiters - - stream.release() - assert payload2._transport is transport - assert not stream._waiters - - -def test_replace_available(loop): - transport = mock.Mock() - stream = StreamWriter(mock.Mock(), transport, loop) - - payload = PayloadWriter(stream, loop, False) - payload2 = stream.replace(payload, PayloadWriter) - assert payload2._transport is transport - assert payload2 not in stream._waiters diff --git a/tests/test_http_writer.py b/tests/test_http_writer.py index 72d43cfe452..2f1339dcdc4 100644 --- a/tests/test_http_writer.py +++ b/tests/test_http_writer.py @@ -1,6 +1,4 @@ """Tests for aiohttp/http_writer.py""" - -import asyncio import zlib from unittest import mock @@ -149,20 +147,3 @@ def test_write_drain(stream, loop): msg.write(b'1', drain=True) assert msg.drain.called assert msg.buffer_size == 0 - - -async def test_multiple_drains(stream, loop): - stream.available = False - msg = http.PayloadWriter(stream, loop, acquire=False) - fut1 = loop.create_task(msg.drain()) - fut2 = loop.create_task(msg.drain()) - - await asyncio.sleep(0) - assert not fut1.done() - assert not fut2.done() - - msg.set_transport(stream.transport) - - await asyncio.sleep(0) - assert fut1.done() - assert fut2.done() diff --git a/tests/test_web_protocol.py b/tests/test_web_protocol.py index 174d9db379d..73a4835ee7b 100644 --- a/tests/test_web_protocol.py +++ b/tests/test_web_protocol.py @@ -100,16 +100,10 @@ async def test_shutdown(srv, loop, transport): assert transport is srv.transport srv._keepalive = True - srv.data_received( - b'GET / HTTP/1.1\r\n' - b'Host: example.com\r\n' - b'Content-Length: 0\r\n\r\n') + task_handler = srv._task_handler - request_handler = srv._request_handlers[-1] - - await asyncio.sleep(0.1, loop=loop) - assert len(srv._waiters) == 1 - assert len(srv._request_handlers) == 1 + assert srv._waiter is not None + assert srv._task_handler is not None t0 = loop.time() await srv.shutdown() @@ -120,44 +114,9 @@ async def test_shutdown(srv, loop, transport): assert transport.close.called assert srv.transport is None - assert not srv._request_handlers - assert request_handler.done() - - -async def test_shutdown_multiple_handlers(srv, loop, transport): - srv.handle_request = mock.Mock() - srv.handle_request.side_effect = helpers.noop - - assert transport is srv.transport - - srv._keepalive = True - srv._max_concurrent_handlers = 2 - srv.data_received( - b'GET / HTTP/1.1\r\n' - b'Host: example.com\r\n' - b'Content-Length: 0\r\n\r\n' - b'GET / HTTP/1.1\r\n' - b'Host: example.com\r\n' - b'Content-Length: 0\r\n\r\n') - - h1, h2 = srv._request_handlers - + assert not srv._task_handler await asyncio.sleep(0.1, loop=loop) - assert len(srv._waiters) == 2 - assert len(srv._request_handlers) == 2 - - t0 = loop.time() - await srv.shutdown() - t1 = loop.time() - - assert t1 - t0 < 0.05, t1-t0 - - assert transport.close.called - assert srv.transport is None - - assert not srv._request_handlers - assert h1.done() - assert h2.done() + assert task_handler.done() async def test_double_shutdown(srv, transport): @@ -176,25 +135,21 @@ async def test_close_after_response(srv, loop, transport): b'GET / HTTP/1.0\r\n' b'Host: example.com\r\n' b'Content-Length: 0\r\n\r\n') - h, = srv._request_handlers + h = srv._task_handler await asyncio.sleep(0.1, loop=loop) - assert len(srv._waiters) == 0 - assert len(srv._request_handlers) == 0 + assert srv._waiter is None + assert srv._task_handler is None assert transport.close.called assert srv.transport is None - assert not srv._request_handlers assert h.done() def test_connection_made(make_srv): srv = make_srv() - assert not srv._request_handlers - srv.connection_made(mock.Mock()) - assert not srv._request_handlers assert not srv._force_close @@ -232,7 +187,7 @@ async def test_connection_lost(srv, loop): b'Content-Length: 0\r\n\r\n') srv._keepalive = True - handle = srv._request_handlers[0] + handle = srv._task_handler await asyncio.sleep(0, loop=loop) # wait for .start() starting srv.connection_lost(None) @@ -240,7 +195,7 @@ async def test_connection_lost(srv, loop): await handle - assert not srv._request_handlers + assert not srv._task_handler def test_srv_keep_alive(srv): @@ -270,7 +225,8 @@ async def test_bad_method(srv, loop, buf): assert buf.startswith(b'HTTP/1.0 400 Bad Request\r\n') -async def test_internal_error(srv, loop, buf): +async def test_data_received_error(srv, loop, buf): + srv.transport = mock.Mock() srv._request_parser = mock.Mock() srv._request_parser.feed_data.side_effect = TypeError @@ -280,6 +236,8 @@ async def test_internal_error(srv, loop, buf): await asyncio.sleep(0, loop=loop) assert buf.startswith(b'HTTP/1.0 500 Internal Server Error\r\n') + assert srv.transport.close.called + assert srv._error_handler is None async def test_line_too_long(srv, loop, buf): @@ -346,7 +304,7 @@ async def handle(request): b'Host: example.com\r\n' b'Content-Length: 0\r\n\r\n') - await srv._request_handlers[0] + await srv._task_handler assert request_handler.called srv.logger.exception.assert_called_with( "Unhandled runtime exception", exc_info=mock.ANY) @@ -372,7 +330,7 @@ def close(): b'Host: example.com\r\n' b'Content-Length: 50000\r\n\r\n') - await srv._request_handlers[0] + await srv._task_handler assert request_handler.called assert closed srv.logger.exception.assert_called_with( @@ -390,7 +348,7 @@ def close(): transport.close.side_effect = close - srv = make_srv(lingering_time=0, max_concurrent_handlers=2) + srv = make_srv(lingering_time=0) srv.connection_made(transport) srv.logger.exception = mock.Mock() @@ -415,11 +373,11 @@ async def handle(request): b'Host: example.com\r\n' b'Content-Length: 50000\r\n\r\n') - assert len(srv._request_handlers) == 2 + assert srv._task_handler await asyncio.sleep(0, loop=loop) - await srv._request_handlers[0] + await srv._task_handler assert normal_completed assert request_handler.called assert closed @@ -507,7 +465,7 @@ async def handle_request(message, payload, writer): srv.handle_request = handle_request async def cancel(): - srv._request_handlers[0].cancel() + srv._task_handler.cancel() srv.data_received( b'GET / HTTP/1.0\r\n' @@ -515,7 +473,7 @@ async def cancel(): b'Host: example.com\r\n\r\n') loop.run_until_complete( - asyncio.gather(srv._request_handlers[0], cancel(), loop=loop)) + asyncio.gather(srv._task_handler, cancel(), loop=loop)) assert log.debug.called @@ -533,7 +491,7 @@ def test_handle_cancelled(make_srv, loop, transport): b'GET / HTTP/1.0\r\n' b'Host: example.com\r\n\r\n') - r_handler = srv._request_handlers[0] + r_handler = srv._task_handler assert loop.run_until_complete(r_handler) is None @@ -550,7 +508,7 @@ def test_handle_500(srv, loop, buf, transport, request_handler): srv.data_received( b'GET / HTTP/1.0\r\n' b'Host: example.com\r\n\r\n') - loop.run_until_complete(srv._request_handlers[0]) + loop.run_until_complete(srv._task_handler) assert b'500 Internal Server Error' in buf @@ -570,13 +528,14 @@ async def test_keep_alive(make_srv, loop, transport, ceil): b'Content-Length: 0\r\n\r\n') await asyncio.sleep(0, loop=loop) - assert len(srv._waiters) == 1 + waiter = srv._waiter + assert waiter assert srv._keepalive_handle is not None assert not transport.close.called await asyncio.sleep(0.1, loop=loop) assert transport.close.called - assert srv._waiters[0].cancelled + assert waiter.cancelled def test_srv_process_request_without_timeout(make_srv, loop, transport): @@ -587,7 +546,7 @@ def test_srv_process_request_without_timeout(make_srv, loop, transport): b'GET / HTTP/1.0\r\n' b'Host: example.com\r\n\r\n') - loop.run_until_complete(srv._request_handlers[0]) + loop.run_until_complete(srv._task_handler) assert transport.close.called @@ -648,7 +607,6 @@ def test_rudimentary_transport(srv, loop): async def test_close(srv, loop, transport): transport.close.side_effect = partial(srv.connection_lost, None) - srv._max_concurrent_handlers = 2 srv.connection_made(transport) srv.handle_request = mock.Mock() @@ -666,12 +624,12 @@ async def test_close(srv, loop, transport): b'Content-Length: 0\r\n\r\n') await asyncio.sleep(0, loop=loop) - assert len(srv._request_handlers) == 2 - assert len(srv._waiters) == 2 + assert srv._task_handler + assert srv._waiter srv.close() await asyncio.sleep(0, loop=loop) - assert len(srv._request_handlers) == 0 + assert srv._task_handler is None assert srv.transport is None assert transport.close.called @@ -680,7 +638,6 @@ async def test_pipeline_multiple_messages( srv, loop, transport, request_handler ): transport.close.side_effect = partial(srv.connection_lost, None) - srv._max_concurrent_handlers = 1 processed = 0 @@ -702,13 +659,13 @@ async def handle(request): b'Host: example.com\r\n' b'Content-Length: 0\r\n\r\n') - assert len(srv._request_handlers) == 1 - assert len(srv._messages) == 1 - assert len(srv._waiters) == 0 + assert srv._task_handler is not None + assert len(srv._messages) == 2 + assert srv._waiter is not None await asyncio.sleep(0, loop=loop) - assert len(srv._request_handlers) == 1 - assert len(srv._waiters) == 1 + assert srv._task_handler is not None + assert srv._waiter is not None assert processed == 2 @@ -717,7 +674,6 @@ async def test_pipeline_response_order( ): transport.close.side_effect = partial(srv.connection_lost, None) srv._keepalive = True - srv._max_concurrent_handlers = 2 processed = [] @@ -756,7 +712,27 @@ async def handle2(request): b'Content-Length: 0\r\n\r\n') await asyncio.sleep(0, loop=loop) - assert len(srv._request_handlers) == 2 + assert srv._task_handler is not None await asyncio.sleep(0.1, loop=loop) assert processed == [1, 2] + + +def test_data_received_close(srv): + srv.close() + srv.data_received( + b'GET / HTTP/1.1\r\n' + b'Host: example.com\r\n' + b'Content-Length: 0\r\n\r\n') + + assert not srv._messages + + +def test_data_received_force_close(srv): + srv.force_close() + srv.data_received( + b'GET / HTTP/1.1\r\n' + b'Host: example.com\r\n' + b'Content-Length: 0\r\n\r\n') + + assert not srv._messages diff --git a/tests/test_web_request_handler.py b/tests/test_web_request_handler.py index a6360389f59..aeeedd04aee 100644 --- a/tests/test_web_request_handler.py +++ b/tests/test_web_request_handler.py @@ -1,7 +1,7 @@ from unittest import mock from aiohttp import web -from aiohttp.test_utils import make_mocked_coro, make_mocked_request +from aiohttp.test_utils import make_mocked_coro def test_repr(loop): @@ -9,13 +9,10 @@ def test_repr(loop): manager = app.make_handler(loop=loop) handler = manager() - assert '' == repr(handler) + assert '' == repr(handler) handler.transport = object() - request = make_mocked_request('GET', '/index.html') - handler._request = request - # assert '' == repr(handler) - assert '' == repr(handler) + assert '' == repr(handler) def test_connections(loop):