Skip to content

Commit bb646d1

Browse files
thehesiodasvetlov
authored andcommitted
option to disable automatic client response body decompression (#2110)
* work on feature to disable automatic response body decompression * add a missing folder to clean * bugfix * rename * add unittests * flake fixes * another pep fix * fix unittest * use constants * fix spelling * Update client_reference.rst Pin version to 2.3 * fix unittests * fix test * pep
1 parent f8fd352 commit bb646d1

9 files changed

+97
-29
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ clean:
104104
@rm -f .develop
105105
@rm -f .flake
106106
@rm -f .install-deps
107+
@rm -rf aiohttp.egg-info
107108

108109
doc:
109110
@make -C docs html SPHINXOPTS="-W -E"

aiohttp/_http_parser.pyx

+8-5
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ cdef class HttpParser:
5757
object _payload
5858
bint _payload_error
5959
object _payload_exception
60-
object _last_error
60+
object _last_error
61+
bint _auto_decompress
6162

6263
Py_buffer py_buf
6364

@@ -80,7 +81,7 @@ cdef class HttpParser:
8081
object protocol, object loop, object timer=None,
8182
size_t max_line_size=8190, size_t max_headers=32768,
8283
size_t max_field_size=8190, payload_exception=None,
83-
response_with_body=True):
84+
response_with_body=True, auto_decompress=True):
8485
cparser.http_parser_init(self._cparser, mode)
8586
self._cparser.data = <void*>self
8687
self._cparser.content_length = 0
@@ -106,6 +107,7 @@ cdef class HttpParser:
106107
self._max_field_size = max_field_size
107108
self._response_with_body = response_with_body
108109
self._upgraded = False
110+
self._auto_decompress = auto_decompress
109111

110112
self._csettings.on_url = cb_on_url
111113
self._csettings.on_status = cb_on_status
@@ -194,7 +196,7 @@ cdef class HttpParser:
194196
payload = EMPTY_PAYLOAD
195197

196198
self._payload = payload
197-
if encoding is not None:
199+
if encoding is not None and self._auto_decompress:
198200
self._payload = DeflateBuffer(payload, encoding)
199201

200202
if not self._response_with_body:
@@ -301,10 +303,11 @@ cdef class HttpResponseParserC(HttpParser):
301303
def __init__(self, protocol, loop, timer=None,
302304
size_t max_line_size=8190, size_t max_headers=32768,
303305
size_t max_field_size=8190, payload_exception=None,
304-
response_with_body=True, read_until_eof=False):
306+
response_with_body=True, read_until_eof=False,
307+
auto_decompress=True):
305308
self._init(cparser.HTTP_RESPONSE, protocol, loop, timer,
306309
max_line_size, max_headers, max_field_size,
307-
payload_exception, response_with_body)
310+
payload_exception, response_with_body, auto_decompress)
308311

309312

310313
cdef int cb_on_message_begin(cparser.http_parser* parser) except -1:

aiohttp/client.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ def __init__(self, *, connector=None, loop=None, cookies=None,
5454
ws_response_class=ClientWebSocketResponse,
5555
version=http.HttpVersion11,
5656
cookie_jar=None, connector_owner=True, raise_for_status=False,
57-
read_timeout=sentinel, conn_timeout=None):
57+
read_timeout=sentinel, conn_timeout=None,
58+
auto_decompress=True):
5859

5960
implicit_loop = False
6061
if loop is None:
@@ -102,6 +103,7 @@ def __init__(self, *, connector=None, loop=None, cookies=None,
102103
else DEFAULT_TIMEOUT)
103104
self._conn_timeout = conn_timeout
104105
self._raise_for_status = raise_for_status
106+
self._auto_decompress = auto_decompress
105107

106108
# Convert to list of tuples
107109
if headers:
@@ -223,7 +225,7 @@ def _request(self, method, url, *,
223225
expect100=expect100, loop=self._loop,
224226
response_class=self._response_class,
225227
proxy=proxy, proxy_auth=proxy_auth, timer=timer,
226-
session=self)
228+
session=self, auto_decompress=self._auto_decompress)
227229

228230
# connection timeout
229231
try:

aiohttp/client_proto.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,16 @@ def set_parser(self, parser, payload):
128128
def set_response_params(self, *, timer=None,
129129
skip_payload=False,
130130
skip_status_codes=(),
131-
read_until_eof=False):
131+
read_until_eof=False,
132+
auto_decompress=True):
132133
self._skip_payload = skip_payload
133134
self._skip_status_codes = skip_status_codes
134135
self._read_until_eof = read_until_eof
135136
self._parser = HttpResponseParser(
136137
self, self._loop, timer=timer,
137138
payload_exception=ClientPayloadError,
138-
read_until_eof=read_until_eof)
139+
read_until_eof=read_until_eof,
140+
auto_decompress=auto_decompress)
139141

140142
if self._tail:
141143
data, self._tail = self._tail, b''

aiohttp/client_reqrep.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def __init__(self, method, url, *,
6666
chunked=None, expect100=False,
6767
loop=None, response_class=None,
6868
proxy=None, proxy_auth=None, proxy_from_env=False,
69-
timer=None, session=None):
69+
timer=None, session=None, auto_decompress=True):
7070

7171
if loop is None:
7272
loop = asyncio.get_event_loop()
@@ -88,6 +88,7 @@ def __init__(self, method, url, *,
8888
self.length = None
8989
self.response_class = response_class or ClientResponse
9090
self._timer = timer if timer is not None else TimerNoop()
91+
self._auto_decompress = auto_decompress
9192

9293
if loop.get_debug():
9394
self._source_traceback = traceback.extract_stack(sys._getframe(1))
@@ -406,7 +407,8 @@ def send(self, conn):
406407
self.response = self.response_class(
407408
self.method, self.original_url,
408409
writer=self._writer, continue100=self._continue, timer=self._timer,
409-
request_info=self.request_info
410+
request_info=self.request_info,
411+
auto_decompress=self._auto_decompress
410412
)
411413

412414
self.response._post_init(self.loop, self._session)
@@ -450,7 +452,7 @@ class ClientResponse(HeadersMixin):
450452

451453
def __init__(self, method, url, *,
452454
writer=None, continue100=None, timer=None,
453-
request_info=None):
455+
request_info=None, auto_decompress=True):
454456
assert isinstance(url, URL)
455457

456458
self.method = method
@@ -465,6 +467,7 @@ def __init__(self, method, url, *,
465467
self._history = ()
466468
self._request_info = request_info
467469
self._timer = timer if timer is not None else TimerNoop()
470+
self._auto_decompress = auto_decompress
468471

469472
@property
470473
def url(self):
@@ -550,7 +553,8 @@ def start(self, connection, read_until_eof=False):
550553
timer=self._timer,
551554
skip_payload=self.method.lower() == 'head',
552555
skip_status_codes=(204, 304),
553-
read_until_eof=read_until_eof)
556+
read_until_eof=read_until_eof,
557+
auto_decompress=self._auto_decompress)
554558

555559
with self._timer:
556560
while True:

aiohttp/http_parser.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def __init__(self, protocol=None, loop=None,
5959
max_line_size=8190, max_headers=32768, max_field_size=8190,
6060
timer=None, code=None, method=None, readall=False,
6161
payload_exception=None,
62-
response_with_body=True, read_until_eof=False):
62+
response_with_body=True, read_until_eof=False,
63+
auto_decompress=True):
6364
self.protocol = protocol
6465
self.loop = loop
6566
self.max_line_size = max_line_size
@@ -78,6 +79,7 @@ def __init__(self, protocol=None, loop=None,
7879
self._upgraded = False
7980
self._payload = None
8081
self._payload_parser = None
82+
self._auto_decompress = auto_decompress
8183

8284
def feed_eof(self):
8385
if self._payload_parser is not None:
@@ -162,7 +164,8 @@ def feed_data(self, data,
162164
chunked=msg.chunked, method=method,
163165
compression=msg.compression,
164166
code=self.code, readall=self.readall,
165-
response_with_body=self.response_with_body)
167+
response_with_body=self.response_with_body,
168+
auto_decompress=self._auto_decompress)
166169
if not payload_parser.done:
167170
self._payload_parser = payload_parser
168171
elif method == METH_CONNECT:
@@ -171,7 +174,8 @@ def feed_data(self, data,
171174
self._upgraded = True
172175
self._payload_parser = HttpPayloadParser(
173176
payload, method=msg.method,
174-
compression=msg.compression, readall=True)
177+
compression=msg.compression, readall=True,
178+
auto_decompress=self._auto_decompress)
175179
else:
176180
if (getattr(msg, 'code', 100) >= 199 and
177181
length is None and self.read_until_eof):
@@ -182,7 +186,8 @@ def feed_data(self, data,
182186
chunked=msg.chunked, method=method,
183187
compression=msg.compression,
184188
code=self.code, readall=True,
185-
response_with_body=self.response_with_body)
189+
response_with_body=self.response_with_body,
190+
auto_decompress=self._auto_decompress)
186191
if not payload_parser.done:
187192
self._payload_parser = payload_parser
188193
else:
@@ -432,18 +437,19 @@ class HttpPayloadParser:
432437
def __init__(self, payload,
433438
length=None, chunked=False, compression=None,
434439
code=None, method=None,
435-
readall=False, response_with_body=True):
440+
readall=False, response_with_body=True, auto_decompress=True):
436441
self.payload = payload
437442

438443
self._length = 0
439444
self._type = ParseState.PARSE_NONE
440445
self._chunk = ChunkState.PARSE_CHUNKED_SIZE
441446
self._chunk_size = 0
442447
self._chunk_tail = b''
448+
self._auto_decompress = auto_decompress
443449
self.done = False
444450

445451
# payload decompression wrapper
446-
if (response_with_body and compression):
452+
if response_with_body and compression and self._auto_decompress:
447453
payload = DeflateBuffer(payload, compression)
448454

449455
# payload parser

changes/2110.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
add ability to disable automatic response decompression

docs/client_reference.rst

+6-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ The client session supports the context manager protocol for self closing.
4848
cookie_jar=None, read_timeout=None, \
4949
conn_timeout=None, \
5050
raise_for_status=False, \
51-
connector_owner=True)
51+
connector_owner=True, \
52+
auto_decompress=True)
5253

5354
The class for creating client sessions and making requests.
5455

@@ -138,6 +139,10 @@ The client session supports the context manager protocol for self closing.
138139

139140
.. versionadded:: 2.1
140141

142+
:param bool auto_decompress: Automatically decompress response body
143+
144+
.. versionadded:: 2.3
145+
141146
.. attribute:: closed
142147

143148
``True`` if the session has been closed, ``False`` otherwise.

tests/test_test_utils.py

+53-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import gzip
23
from unittest import mock
34

45
import pytest
@@ -14,11 +15,20 @@
1415
teardown_test_loop, unittest_run_loop)
1516

1617

17-
def _create_example_app():
18+
_hello_world_str = "Hello, world"
19+
_hello_world_bytes = _hello_world_str.encode('utf-8')
20+
_hello_world_gz = gzip.compress(_hello_world_bytes)
21+
1822

23+
def _create_example_app():
1924
@asyncio.coroutine
2025
def hello(request):
21-
return web.Response(body=b"Hello, world")
26+
return web.Response(body=_hello_world_bytes)
27+
28+
@asyncio.coroutine
29+
def gzip_hello(request):
30+
return web.Response(body=_hello_world_gz,
31+
headers={'Content-Encoding': 'gzip'})
2232

2333
@asyncio.coroutine
2434
def websocket_handler(request):
@@ -36,12 +46,13 @@ def websocket_handler(request):
3646

3747
@asyncio.coroutine
3848
def cookie_handler(request):
39-
resp = web.Response(body=b"Hello, world")
49+
resp = web.Response(body=_hello_world_bytes)
4050
resp.set_cookie('cookie', 'val')
4151
return resp
4252

4353
app = web.Application()
4454
app.router.add_route('*', '/', hello)
55+
app.router.add_route('*', '/gzip_hello', gzip_hello)
4556
app.router.add_route('*', '/websocket', websocket_handler)
4657
app.router.add_route('*', '/cookie', cookie_handler)
4758
return app
@@ -58,7 +69,40 @@ def test_get_route():
5869
resp = yield from client.request("GET", "/")
5970
assert resp.status == 200
6071
text = yield from resp.text()
61-
assert "Hello, world" in text
72+
assert _hello_world_str == text
73+
74+
loop.run_until_complete(test_get_route())
75+
76+
77+
def test_auto_gzip_decompress():
78+
with loop_context() as loop:
79+
app = _create_example_app()
80+
with _TestClient(_TestServer(app, loop=loop), loop=loop) as client:
81+
82+
@asyncio.coroutine
83+
def test_get_route():
84+
nonlocal client
85+
resp = yield from client.request("GET", "/gzip_hello")
86+
assert resp.status == 200
87+
data = yield from resp.read()
88+
assert data == _hello_world_bytes
89+
90+
loop.run_until_complete(test_get_route())
91+
92+
93+
def test_noauto_gzip_decompress():
94+
with loop_context() as loop:
95+
app = _create_example_app()
96+
with _TestClient(_TestServer(app, loop=loop), loop=loop,
97+
auto_decompress=False) as client:
98+
99+
@asyncio.coroutine
100+
def test_get_route():
101+
nonlocal client
102+
resp = yield from client.request("GET", "/gzip_hello")
103+
assert resp.status == 200
104+
data = yield from resp.read()
105+
assert data == _hello_world_gz
62106

63107
loop.run_until_complete(test_get_route())
64108

@@ -73,7 +117,7 @@ def test_get_route():
73117
resp = yield from client.request("GET", "/")
74118
assert resp.status == 200
75119
text = yield from resp.text()
76-
assert "Hello, world" in text
120+
assert _hello_world_str == text
77121

78122
loop.run_until_complete(test_get_route())
79123

@@ -102,15 +146,15 @@ def test_example_with_loop(self):
102146
request = yield from self.client.request("GET", "/")
103147
assert request.status == 200
104148
text = yield from request.text()
105-
assert "Hello, world" in text
149+
assert _hello_world_str == text
106150

107151
def test_example(self):
108152
@asyncio.coroutine
109153
def test_get_route():
110154
resp = yield from self.client.request("GET", "/")
111155
assert resp.status == 200
112156
text = yield from resp.text()
113-
assert "Hello, world" in text
157+
assert _hello_world_str == text
114158

115159
self.loop.run_until_complete(test_get_route())
116160

@@ -141,7 +185,7 @@ def test_get_route():
141185
resp = yield from test_client.request("GET", "/")
142186
assert resp.status == 200
143187
text = yield from resp.text()
144-
assert "Hello, world" in text
188+
assert _hello_world_str == text
145189

146190
loop.run_until_complete(test_get_route())
147191

@@ -176,7 +220,7 @@ def test_test_client_methods(method, loop, test_client):
176220
resp = yield from getattr(test_client, method)("/")
177221
assert resp.status == 200
178222
text = yield from resp.text()
179-
assert "Hello, world" in text
223+
assert _hello_world_str == text
180224

181225

182226
@asyncio.coroutine

0 commit comments

Comments
 (0)