Repository: aio-libs/aiohttp

Issue:
Client hangs on 206 Partial Content response with Content-Encoding: gzip
## Long story short

When server responds with 206 to range request - `response.read()` hangs.
Also `requests` library handles the same scenarios successfully.

## Expected behaviour

Partial content is a valid content when responding to a range request. The client should not hang and just decode as much as possible.

## Actual behaviour

The `response.read()` hangs indefinitely if Content-Encoding: gzip. Does not hang when Content-Encoding is deflate or brotli.

## Steps to reproduce

```python
import asyncio

import aiohttp
import aiohttp.web
import requests

async def client():
    async with aiohttp.ClientSession(headers={"Range": "bytes=0-1000"}) as session:
        # async with session.get("https://www.airbnb.com") as response:
        async with session.get("https://www.tripadvisor.com") as response:
            await response.read()
            print("Never reached")

print(requests.get("https://www.tripadvisor.com", headers={"Range": "bytes=0-1000"}).text)
print(requests.get("https://www.airbnb.com", headers={"Range": "bytes=0-1000"}).text)

asyncio.get_event_loop().run_until_complete(client())
```

## Your environment

aiohttp==3.3.2
requests==2.19.1
macOS 10.13.5
no proxy
aiohttp client



Discussion:
After some investigation it seems, that this happens only for 206 Partial Content responses with gzip Content-Encoding. Created a pull-request which solves my use case.

Resolving patch:
diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py
--- a/aiohttp/http_parser.py
+++ b/aiohttp/http_parser.py
@@ -662,7 +662,7 @@ def feed_eof(self):
 
         if chunk or self.size > 0:
             self.out.feed_data(chunk, len(chunk))
-            if self.encoding != 'br' and not self.decompressor.eof:
+            if self.encoding == 'deflate' and not self.decompressor.eof:
                 raise ContentEncodingError('deflate')
 
         self.out.feed_eof()


Test patch:
diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py
--- a/tests/test_http_parser.py
+++ b/tests/test_http_parser.py
@@ -902,7 +902,7 @@ def test_feed_eof(self, stream):
         assert [b'line'] == list(d for d, _ in buf._buffer)
         assert buf._eof
 
-    def test_feed_eof_err(self, stream):
+    def test_feed_eof_err_deflate(self, stream):
         buf = aiohttp.FlowControlDataQueue(stream)
         dbuf = DeflateBuffer(buf, 'deflate')
 
@@ -913,6 +913,28 @@ def test_feed_eof_err(self, stream):
         with pytest.raises(http_exceptions.ContentEncodingError):
             dbuf.feed_eof()
 
+    def test_feed_eof_no_err_gzip(self, stream):
+        buf = aiohttp.FlowControlDataQueue(stream)
+        dbuf = DeflateBuffer(buf, 'gzip')
+
+        dbuf.decompressor = mock.Mock()
+        dbuf.decompressor.flush.return_value = b'line'
+        dbuf.decompressor.eof = False
+
+        dbuf.feed_eof()
+        assert [b'line'] == list(d for d, _ in buf._buffer)
+
+    def test_feed_eof_no_err_brotli(self, stream):
+        buf = aiohttp.FlowControlDataQueue(stream)
+        dbuf = DeflateBuffer(buf, 'br')
+
+        dbuf.decompressor = mock.Mock()
+        dbuf.decompressor.flush.return_value = b'line'
+        dbuf.decompressor.eof = False
+
+        dbuf.feed_eof()
+        assert [b'line'] == list(d for d, _ in buf._buffer)
+
     def test_empty_body(self, stream):
         buf = aiohttp.FlowControlDataQueue(stream)
         dbuf = DeflateBuffer(buf, 'deflate')