Repository: aio-libs/aiohttp

Issue:
Empty body handled as error (204 No Content)
An empty body handled as error. Although it isn't error. I see aiohttp code, it always trying to decompress empty data and raised.

```
Traceback (most recent call last):
  File "example.py", line 22, in <module>
    fetch_page(rh))
  File "/usr/local/lib/python3.5/asyncio/base_events.py", line 342, in run_until_complete
    return future.result()
  File "/usr/local/lib/python3.5/asyncio/futures.py", line 274, in result
    raise self._exception
  File "/usr/local/lib/python3.5/asyncio/tasks.py", line 239, in _step
    result = coro.send(value)
  File "example.py", line 8, in fetch_page
    print(await Bucket(client, 'ololo').delete('XXXX'))
  File "/usr/home/vg/Develop/aiohttp_riak/aiohttp_riak/Bucket.py", line 100, in delete
    return False
  File "/usr/home/vg/Develop/aiohttp_riak/.venv/lib/python3.5/site-packages/aiohttp/client.py", line 523, in __aexit__
    yield from self._resp.release()
  File "/usr/home/vg/Develop/aiohttp_riak/.venv/lib/python3.5/site-packages/aiohttp/client_reqrep.py", line 656, in release
    chunk = yield from content.readany()
  File "/usr/home/vg/Develop/aiohttp_riak/.venv/lib/python3.5/site-packages/aiohttp/streams.py", line 472, in wrapper
    result = yield from func(self, *args, **kw)
  File "/usr/home/vg/Develop/aiohttp_riak/.venv/lib/python3.5/site-packages/aiohttp/streams.py", line 535, in readany
    return (yield from super().readany())
  File "/usr/home/vg/Develop/aiohttp_riak/.venv/lib/python3.5/site-packages/aiohttp/streams.py", line 265, in readany
    raise self._exception
  File "/usr/home/vg/Develop/aiohttp_riak/.venv/lib/python3.5/site-packages/aiohttp/parsers.py", line 189, in set_parser
    next(p)
  File "/usr/home/vg/Develop/aiohttp_riak/.venv/lib/python3.5/site-packages/aiohttp/protocol.py", line 313, in __call__
    out.feed_eof()
  File "/usr/home/vg/Develop/aiohttp_riak/.venv/lib/python3.5/site-packages/aiohttp/protocol.py", line 387, in feed_eof
    raise errors.ContentEncodingError('deflate')
aiohttp.errors.ContentEncodingError: 400, message='deflate'
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x806696470>
```

```
curl -XDELETE -v localhost:8098/buckets/ololo/keys/XXXX                                                                              
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8098 (#0)
> DELETE /buckets/ololo/keys/XXXX HTTP/1.1
> Host: localhost:8098
> User-Agent: curl/7.45.0
> Accept: */*
> 
< HTTP/1.1 204 No Content
< Vary: Accept-Encoding
< Server: MochiWeb/1.1 WebMachine/1.10.0 (never breaks eye contact)
< Date: Tue, 02 Feb 2016 09:39:46 GMT
< Content-Type: application/json
< Content-Length: 0
< 
* Connection #0 to host localhost left intact
```

```
12:43:49.902869 IP localhost.8098 > localhost.49355: Flags [P.], seq 400:425, ack 244, win 128, options [nop,nop,TS val 586110440 ecr 1045355615], length 25
E..Me.@.@...............Z..<._.).....A.....
".U.>N._..................I*.....
12:43:49.902878 IP localhost.49355 > localhost.8098: Flags [.], ack 425, win 1269, options [nop,nop,TS val 1045355616 ecr 586110440], length 0
E..4e.@.@................_.)Z..U.....(.....
>N.`".U.
12:43:49.918847 IP localhost.49355 > localhost.8098: Flags [P.], seq 244:437, ack 425, win 1275, options [nop,nop,TS val 1045355632 ecr 586110440], length 193
E...e.@.@................_.)Z..U...........
>N.p".U.DELETE /buckets/ololo/keys/XXXX HTTP/1.1
ACCEPT: */*
ACCEPT-ENCODING: gzip, deflate
HOST: 127.0.0.1:8098
USER-AGENT: Python/3.5 aiohttp/0.20.2
CONTENT-LENGTH: 0
CONNECTION: keep-alive


12:43:49.921062 IP localhost.8098 > localhost.49355: Flags [P.], seq 425:654, ack 437, win 128, options [nop,nop,TS val 586110459 ecr 1045355632], length 229
......@.@...............Z..U._.......
".U.>N.pHTTP/1.1 204 No Content
Vary: Accept-Encoding
Server: MochiWeb/1.1 WebMachine/1.10.0 (never breaks eye contact)
Date: Tue, 02 Feb 2016 09:43:49 GMT
Content-Type: application/json
Content-Length: 0
Content-Encoding: gzip


12:43:49.939182 IP localhost.49355 > localhost.8098: Flags [F.], seq 437, ack 654, win 1275, options [nop,nop,TS val 1045355653 ecr 586110459], length 0
E..4e.@.@................_..Z..:.....(.....
>N..".U.
12:43:49.939216 IP localhost.8098 > localhost.49355: Flags [.], ack 438, win 128, options [nop,nop,TS val 586110477 ecr 1045355653], length 0
E..4e.@.@...............Z..:._.......(.....
>N..
12:43:49.939352 IP localhost.8098 > localhost.49355: Flags [F.], seq 654, ack 438, win 128, options [nop,nop,TS val 586110477 ecr 1045355653], length 0
E..4e.@.@...............Z..:._.......(.....
>N..
12:43:49.939369 IP localhost.49355 > localhost.8098: Flags [.], ack 655, win 1275, options [nop,nop,TS val 1045355653 ecr 586110477], length 0
E..4e.@.@................_..Z..;.....(.....
>N..".V
```



Discussion:
Would you provide a Pull Request with fix?

Just ran into this issue and have some additional information. 
Was able to work around the issue by setting the Accept-Encoding header to either identity or application/json (obviously, I was working with a JSON API). 
With either Accept-Encoding selected, you get the 204 status code but avoid the deflate exception that is otherwise raised. 


Resolving patch:
diff --git a/aiohttp/protocol.py b/aiohttp/protocol.py
--- a/aiohttp/protocol.py
+++ b/aiohttp/protocol.py
@@ -286,7 +286,8 @@ def __call__(self, out, buf):
             length = 8
 
         # payload decompression wrapper
-        if self.compression and self.message.compression:
+        if (self.response_with_body and
+                self.compression and self.message.compression):
             out = DeflateBuffer(out, self.message.compression)
 
         # payload parser
diff --git a/aiohttp/web_reqrep.py b/aiohttp/web_reqrep.py
--- a/aiohttp/web_reqrep.py
+++ b/aiohttp/web_reqrep.py
@@ -482,6 +482,10 @@ def enable_compression(self, force=None):
         # Backwards compatibility for when force was a bool <0.17.
         if type(force) == bool:
             force = ContentCoding.deflate if force else ContentCoding.identity
+        elif force is not None:
+            assert isinstance(force, ContentCoding), ("force should one of "
+                                                      "None, bool or "
+                                                      "ContentEncoding")
 
         self._compression = True
         self._compression_force = force


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -222,6 +222,12 @@ def post(self, path, **kwargs):
         url = self._url + path
         return self._session.post(url, **kwargs)
 
+    def delete(self, path, **kwargs):
+        while path.startswith('/'):
+            path = path[1:]
+        url = self._url + path
+        return self._session.delete(url)
+
     def ws_connect(self, path, **kwargs):
         while path.startswith('/'):
             path = path[1:]
diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py
--- a/tests/test_client_functional.py
+++ b/tests/test_client_functional.py
@@ -432,3 +432,22 @@ def handler(request):
         resp = yield from client.get('/', version=aiohttp.HttpVersion11)
         assert resp.status == 200
         resp.close()
+
+
+@pytest.mark.run_loop
+def test_204_with_gzipped_content_encoding(create_app_and_client):
+    @asyncio.coroutine
+    def handler(request):
+        resp = web.StreamResponse(status=204)
+        resp.content_length = 0
+        resp.content_type = 'application/json'
+        # resp.enable_compression(web.ContentCoding.gzip)
+        resp.headers['Content-Encoding'] = 'gzip'
+        yield from resp.prepare(request)
+        return resp
+
+    app, client = yield from create_app_and_client()
+    app.router.add_route('DELETE', '/', handler)
+    resp = yield from client.delete('/')
+    assert resp.status == 204
+    yield from resp.release()