Repository: aio-libs/aiohttp

Issue:
Handle ConnectionResetError
## Long story short

There was introduced `ConnectionResetError` within #2989. What is the preferable way to handle this exception on the server side?

## Expected behaviour

No exceptions or all exceptions are catched and handled.

## Actual behaviour

```
Unhandled exception
Traceback (most recent call last):
  File "/home/decaz/.virtualenvs/test-aiohttp/lib/python3.6/site-packages/aiohttp/web_protocol.py", line 448, in start
    await resp.write_eof()
  File "/usr/lib/python3.6/asyncio/coroutines.py", line 110, in __next__
    return self.gen.send(None)
  File "/home/decaz/.virtualenvs/test-aiohttp/lib/python3.6/site-packages/aiohttp/web_response.py", line 444, in write_eof
    await self._payload_writer.write_eof(data)
  File "/usr/lib/python3.6/asyncio/coroutines.py", line 110, in __next__
    return self.gen.send(None)
  File "/home/decaz/.virtualenvs/test-aiohttp/lib/python3.6/site-packages/aiohttp/http_writer.py", line 138, in write_eof
    self._write(chunk)
  File "/home/decaz/.virtualenvs/test-aiohttp/lib/python3.6/site-packages/aiohttp/http_writer.py", line 67, in _write
    raise ConnectionResetError('Cannot write to closing transport')
ConnectionResetError: Cannot write to closing transport
```

## Steps to reproduce

Close client connection while there is running coroutine on the server side.

## Your environment

Server: aiohttp==3.5.4


Discussion:
[GitMate.io](https://gitmate.io) thinks the contributor most likely able to help you is @asvetlov.

Possibly related issues are https://github.com/aio-libs/aiohttp/issues/720 (My server cant handle Exception.), https://github.com/aio-libs/aiohttp/issues/255 (Handling WebSocket disconnects), https://github.com/aio-libs/aiohttp/issues/850 (How to handle ClientResponseError/ServerDisconnectedError properly), https://github.com/aio-libs/aiohttp/issues/962 (Handling very long connection timeouts), and https://github.com/aio-libs/aiohttp/issues/2489 (drop handle.cancel).
have same problem  with python-socketio  library 
```
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/aiohttp/web_protocol.py", line 447, in start
    await resp.prepare(request)
  File "/usr/local/lib/python3.6/dist-packages/aiohttp/web_response.py", line 353, in prepare
    return await self._start(request)
  File "/usr/local/lib/python3.6/dist-packages/aiohttp/web_response.py", line 667, in _start
    return await super()._start(request)
  File "/usr/local/lib/python3.6/dist-packages/aiohttp/web_response.py", line 410, in _start
    await writer.write_headers(status_line, headers)
  File "/usr/local/lib/python3.6/dist-packages/aiohttp/http_writer.py", line 112, in write_headers
    self._write(buf)
  File "/usr/local/lib/python3.6/dist-packages/aiohttp/http_writer.py", line 67, in _write
    raise ConnectionResetError('Cannot write to closing transport')
ConnectionResetError: Cannot write to closing transport
```
Same here (python 3.7.3, aiohttp 3.5.4).  I am unable to catch this exception in my code, as the traceback is 100% in aiohttp side.  This happens very rarely, and I'm sure is no real problem, but it causes Sentry issues, so is kind of annoying:

```
ConnectionResetError: Cannot write to closing transport
  File "aiohttp/web_protocol.py", line 447, in start
    await resp.prepare(request)
  File "aiohttp/web_response.py", line 353, in prepare
    return await self._start(request)
  File "aiohttp/web_response.py", line 667, in _start
    return await super()._start(request)
  File "aiohttp/web_response.py", line 410, in _start
    await writer.write_headers(status_line, headers)
  File "aiohttp/http_writer.py", line 112, in write_headers
    self._write(buf)
  File "aiohttp/http_writer.py", line 67, in _write
    raise ConnectionResetError('Cannot write to closing transport')
```
@gjcarneiro hi, i fixed that with patching streamwriter
this patch need do before import aiohttp.web
```
from httpwriter_patch import patch_streamwriter
patch_streamwriter()
from aiohttp import web

```
and patch code 

```
def _write_no_exception(self, chunk: bytes) -> None:
    try:
        self.original_write(chunk)
    except ConnectionResetError as exc:
        log.debug('ConnectionResetError exception suppressed')


def patch_streamwriter():
    http_writer.StreamWriter.original_write = http_writer.StreamWriter._write
    http_writer.StreamWriter._write = _write_no_exception
    log.warning('StreamWriter patched to suppress ConnectionResetError\'s')

```



That may not be the 100% correct fix for this: I am worried that `ConnectionResetError` probably needs to be properly handled somewhere, higher up the stack.  But thanks!
@gjcarneiro in my case, ConnectionResetError happens very often when client moves through the city and loses connection then just reconnect 

Resolving patch:
diff --git a/aiohttp/web_protocol.py b/aiohttp/web_protocol.py
--- a/aiohttp/web_protocol.py
+++ b/aiohttp/web_protocol.py
@@ -444,8 +444,22 @@ async def start(self) -> None:
                             raise RuntimeError("Web-handler should return "
                                                "a response instance, "
                                                "got {!r}".format(resp))
-                await resp.prepare(request)
-                await resp.write_eof()
+                try:
+                    prepare_meth = resp.prepare
+                except AttributeError:
+                    if resp is None:
+                        raise RuntimeError("Missing return "
+                                           "statement on request handler")
+                    else:
+                        raise RuntimeError("Web-handler should return "
+                                           "a response instance, "
+                                           "got {!r}".format(resp))
+                try:
+                    await prepare_meth(request)
+                    await resp.write_eof()
+                except ConnectionResetError:
+                    self.log_debug('Ignored premature client disconnection 2')
+                    break
 
                 # notify server about keep-alive
                 self._keepalive = bool(resp.keep_alive)


Test patch:
diff --git a/tests/test_web_protocol.py b/tests/test_web_protocol.py
--- a/tests/test_web_protocol.py
+++ b/tests/test_web_protocol.py
@@ -858,3 +858,30 @@ async def test_two_data_received_without_waking_up_start_task(srv) -> None:
     assert len(srv._messages) == 2
     assert srv._waiter.done()
     await asyncio.sleep(0.01)
+
+
+async def test_client_disconnect(aiohttp_server) -> None:
+
+    async def handler(request):
+        await request.content.read(10)
+        return web.Response()
+
+    logger = mock.Mock()
+    app = web.Application()
+    app._debug = True
+    app.router.add_route('POST', '/', handler)
+    server = await aiohttp_server(app, logger=logger)
+
+    _, writer = await asyncio.open_connection('127.0.0.1', server.port)
+    writer.write("""POST / HTTP/1.1\r
+Connection: keep-alive\r
+Content-Length: 10\r
+Host: localhost:{port}\r
+\r
+""".format(port=server.port).encode("ascii"))
+    await writer.drain()
+    await asyncio.sleep(0.1)
+    writer.write(b"x")
+    writer.close()
+    await asyncio.sleep(0.1)
+    logger.debug.assert_called_with('Ignored premature client disconnection 2')