Skip to content

Commit

Permalink
Raise ContentTypeError by json() when headers does not … (#2136)
Browse files Browse the repository at this point in the history
* Raise ClientResponseContentTypeError by json() when headers does not meet the spec.

* Docs changed to reflect the exception raised by json()

* Added #2136 as a new feature change

* Renamed long ClientResonseContentTypeError to ContentTypeError

* Update client_exceptions.py
  • Loading branch information
pfreixes authored and asvetlov committed Jul 27, 2017
1 parent 9964ddb commit ecc8e63
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 7 deletions.
6 changes: 6 additions & 0 deletions aiohttp/client_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
'ServerFingerprintMismatch',

'ClientResponseError', 'ClientPayloadError',
'ContentTypeError',

'ClientHttpProxyError', 'WSServerHandshakeError')


Expand All @@ -37,6 +39,10 @@ def __init__(self, request_info, history, *,
super().__init__("%s, message='%s'" % (code, message))


class ContentTypeError(ClientResponseError):
"""ContentType found is not valid."""


class WSServerHandshakeError(ClientResponseError):
"""websocket server handshake error."""

Expand Down
4 changes: 2 additions & 2 deletions aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from . import hdrs, helpers, http, payload
from .client_exceptions import (ClientConnectionError, ClientOSError,
ClientResponseError)
ClientResponseError, ContentTypeError)
from .formdata import FormData
from .helpers import PY_35, HeadersMixin, SimpleCookie, TimerNoop, noop
from .http import SERVER_SOFTWARE, HttpVersion10, HttpVersion11, PayloadWriter
Expand Down Expand Up @@ -722,7 +722,7 @@ def json(self, *, encoding=None, loads=json.loads,
if content_type:
ctype = self.headers.get(hdrs.CONTENT_TYPE, '').lower()
if content_type not in ctype:
raise ClientResponseError(
raise ContentTypeError(
self.request_info,
self.history,
message=('Attempt to decode JSON with '
Expand Down
2 changes: 2 additions & 0 deletions changes/2136.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
json() raises a ContentTypeError exception if the content-type
does not meet the requirements instead of raising a generic ClientResponseError.
10 changes: 7 additions & 3 deletions docs/client_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,9 @@ Response object
Close underlying connection if data reading gets an error,
release connection otherwise.

Raise an :exc:`aiohttp.ClientResponseError` if the data can't
be read.

:return bytes: read *BODY*.

.. seealso:: :meth:`close`, :meth:`release`.
Expand Down Expand Up @@ -1104,15 +1107,16 @@ Response object
content_type='application/json')

Read response's body as *JSON*, return :class:`dict` using
specified *encoding* and *loader*.
specified *encoding* and *loader*. If data is not still available
a ``read`` call will be done,

If *encoding* is ``None`` content encoding is autocalculated
using :term:`cchardet` or :term:`chardet` as fallback if
*cchardet* is not available.

if response's `content-type` does not match `content_type` parameter
:exc:`aiohttp.ClientResponseError` get raised. To disable content type
check pass ``None`` value.
:exc:`aiohttp.ContentTypeError` get raised.
To disable content type check pass ``None`` value.

:param str encoding: text encoding used for *BODY* decoding, or
``None`` for encoding autodetection
Expand Down
13 changes: 11 additions & 2 deletions tests/test_client_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,18 +352,27 @@ def custom(content):


@asyncio.coroutine
def test_json_no_content(loop, session):
def test_json_invalid_content_type(loop, session):
response = ClientResponse('get', URL('http://def-cl-resp.org'))
response._post_init(loop, session)
response.headers = {
'Content-Type': 'data/octet-stream'}
response._content = b''

with pytest.raises(aiohttp.ClientResponseError) as info:
with pytest.raises(aiohttp.ContentTypeError) as info:
yield from response.json()

assert info.value.request_info == response.request_info


@asyncio.coroutine
def test_json_no_content(loop, session):
response = ClientResponse('get', URL('http://def-cl-resp.org'))
response._post_init(loop, session)
response.headers = {
'Content-Type': 'data/octet-stream'}
response._content = b''

res = yield from response.json(content_type=None)
assert res is None

Expand Down

0 comments on commit ecc8e63

Please sign in to comment.