Repository: aio-libs/aiohttp

Issue:
Sending 0 byte files now triggers an exception in 3.7.0
🐞 **Describe the bug**
<!-- A clear and concise description of what the bug is, on the next line. -->
#4517 caused a regression with 0 byte files.


💡 **To Reproduce**

Send a 0 byte file with `aiohttp.web.FileResponse` on python 3.7 or later


💡 **Expected behavior**

No exception


📋 **Logs/tracebacks**
<!-- If applicable, add logs/tracebacks to help explain your problem. -->
```python-traceback (paste your traceback in the next line)
Traceback (most recent call last):
  File "/__w/core/core/venv/lib/python3.7/site-packages/aiohttp/web_protocol.py", line 461, in start
    resp, reset = await task
  File "/__w/core/core/venv/lib/python3.7/site-packages/aiohttp/web_protocol.py", line 414, in _handle_request
    reset = await self.finish_response(request, resp, start_time)
  File "/__w/core/core/venv/lib/python3.7/site-packages/aiohttp/web_protocol.py", line 568, in finish_response
    await prepare_meth(request)
  File "/__w/core/core/venv/lib/python3.7/site-packages/aiohttp/web_fileresponse.py", line 373, in prepare
    return await self._sendfile(request, fobj, offset, count)
  File "/__w/core/core/venv/lib/python3.7/site-packages/aiohttp/web_fileresponse.py", line 191, in _sendfile_system
    await writer.sendfile()
  File "/__w/core/core/venv/lib/python3.7/site-packages/aiohttp/web_fileresponse.py", line 107, in sendfile
    self._count
  File "/usr/local/lib/python3.7/asyncio/base_events.py", line 1044, in sendfile
    offset, count)
  File "/usr/local/lib/python3.7/asyncio/selector_events.py", line 551, in _sendfile_native
    fallback=False)
  File "/usr/local/lib/python3.7/asyncio/base_events.py", line 788, in sock_sendfile
    self._check_sendfile_params(sock, file, offset, count)
  File "/usr/local/lib/python3.7/asyncio/base_events.py", line 842, in _check_sendfile_params
    "count must be a positive integer (got {!r})".format(count))
ValueError: count must be a positive integer (got 0)
```

📋 **Your version of the Python**
<!-- Attach your version of the Python. -->
```console
$ python --version
Python 3.8.1
...
```

📋 **Your version of the aiohttp/yarl/multidict distributions**
<!-- Attach your version of the distributions in the code blocks below. -->
```console
$ python -m pip show aiohttp
...
```
```console
$ python -m pip show multidict
...
```
```console
$ python -m pip show yarl
...
```

📋 **Additional context**
<!-- Add any other context about the problem here, in the next line. -->

<!-- Describe the environment you have that lead to your issue.
     This includes aiohttp version, OS, proxy server and other bits that
     are related to your case.

     IMPORTANT: aiohttp is both server framework and client library.
     For getting rid of confusing please put 'server', 'client' or 'both'
     word here.
     -->



Discussion:

Resolving patch:
diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py
--- a/aiohttp/web_fileresponse.py
+++ b/aiohttp/web_fileresponse.py
@@ -98,7 +98,13 @@ async def sendfile(self) -> None:
         if hasattr(loop, "sendfile"):
             # Python 3.7+
             self.transport.write(data)
-            await loop.sendfile(self.transport, self._fobj, self._offset, self._count)
+            if self._count != 0:
+                await loop.sendfile(
+                    self.transport,
+                    self._fobj,
+                    self._offset,
+                    self._count
+                )
             await super().write_eof()
             return
 


Test patch:
diff --git a/tests/data.zero_bytes b/tests/data.zero_bytes
new file mode 100644
diff --git a/tests/test_web_sendfile_functional.py b/tests/test_web_sendfile_functional.py
--- a/tests/test_web_sendfile_functional.py
+++ b/tests/test_web_sendfile_functional.py
@@ -44,6 +44,25 @@ async def handler(request):
     await resp.release()
 
 
+async def test_zero_bytes_file_ok(aiohttp_client, sender) -> None:
+    filepath = pathlib.Path(__file__).parent / "data.zero_bytes"
+
+    async def handler(request):
+        return sender(filepath)
+
+    app = web.Application()
+    app.router.add_get("/", handler)
+    client = await aiohttp_client(app)
+
+    resp = await client.get("/")
+    assert resp.status == 200
+    txt = await resp.text()
+    assert "" == txt.rstrip()
+    assert "application/octet-stream" == resp.headers["Content-Type"]
+    assert resp.headers.get("Content-Encoding") is None
+    await resp.release()
+
+
 async def test_static_file_ok_string_path(aiohttp_client, sender) -> None:
     filepath = pathlib.Path(__file__).parent / "data.unknown_mime_type"