diff --git a/CHANGES/2631.feature b/CHANGES/2631.feature new file mode 100644 index 00000000000..fe826f0755d --- /dev/null +++ b/CHANGES/2631.feature @@ -0,0 +1,2 @@ +Raise ``TooManyRedirects`` exception when client gets redirected too many times +instead of returning last response. diff --git a/aiohttp/client.py b/aiohttp/client.py index 4f6c6e78a01..d0e0bb6b39a 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -18,7 +18,8 @@ from . import hdrs, http, payload from .client_exceptions import * # noqa from .client_exceptions import (ClientError, ClientOSError, InvalidURL, - ServerTimeoutError, WSServerHandshakeError) + ServerTimeoutError, TooManyRedirects, + WSServerHandshakeError) from .client_reqrep import * # noqa from .client_reqrep import ClientRequest, ClientResponse, _merge_ssl_params from .client_ws import ClientWebSocketResponse @@ -360,7 +361,8 @@ async def _request(self, method, url, *, history.append(resp) if max_redirects and redirects >= max_redirects: resp.close() - break + raise TooManyRedirects( + history[0].request_info, tuple(history)) else: resp.release() diff --git a/aiohttp/client_exceptions.py b/aiohttp/client_exceptions.py index 65e38c50287..34b72daf76f 100644 --- a/aiohttp/client_exceptions.py +++ b/aiohttp/client_exceptions.py @@ -93,6 +93,10 @@ class ClientHttpProxyError(ClientResponseError): """ +class TooManyRedirects(ClientResponseError): + """Client was redirected too many times.""" + + class ClientConnectionError(ClientError): """Base class for client socket errors.""" diff --git a/docs/client_reference.rst b/docs/client_reference.rst index 584158fe814..669359a92b5 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -251,6 +251,9 @@ The client session supports the context manager protocol for self closing. :param bool allow_redirects: If set to ``False``, do not follow redirects. ``True`` by default (optional). + :param int max_redirects: Maximum number of redirects to follow. + ``10`` by default. + :param bool compress: Set to ``True`` if request has to be compressed with deflate encoding. If `compress` can not be combined with a *Content-Encoding* and *Content-Length* headers. @@ -501,7 +504,7 @@ The client session supports the context manager protocol for self closing. :param bool autoclose: Automatically close websocket connection on close message from server. If *autoclose* is False - them close procedure has to be handled manually. + then close procedure has to be handled manually. ``True`` by default :param bool autoping: automatically send *pong* on *ping* @@ -514,7 +517,7 @@ The client session supports the context manager protocol for self closing. reception.(optional) :param str origin: Origin header to send to server(optional) - + :param dict headers: HTTP Headers to send with the request (optional) @@ -565,7 +568,7 @@ The client session supports the context manager protocol for self closing. authority channel, supported SSL options etc. .. versionadded:: 2.3 - + .. deprecated:: 3.0 Use ``ssl=ssl_context`` @@ -1727,6 +1730,18 @@ Response errors .. versionadded:: 2.3 + +.. class:: TooManyRedirects + + Client was redirected too many times. + + Maximum number of redirects can be configured by using + parameter ``max_redirects`` in :meth:`request`. + + Derived from :exc:`ClientResponseError` + + .. versionadded:: 3.2 + Connection errors ^^^^^^^^^^^^^^^^^ diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py index 1124e55e3f6..4e5f89e4602 100644 --- a/tests/test_client_functional.py +++ b/tests/test_client_functional.py @@ -16,6 +16,7 @@ import aiohttp from aiohttp import Fingerprint, ServerFingerprintMismatch, hdrs, web from aiohttp.abc import AbstractResolver +from aiohttp.client_exceptions import TooManyRedirects from aiohttp.test_utils import unused_port @@ -914,10 +915,11 @@ async def redirect(request): app.router.add_get(r'/redirect/{count:\d+}', redirect) client = await aiohttp_client(app) - resp = await client.get('/redirect/5', max_redirects=2) - assert 302 == resp.status - assert 2 == len(resp.history) - resp.close() + with pytest.raises(TooManyRedirects) as ctx: + await client.get('/redirect/5', max_redirects=2) + assert 2 == len(ctx.value.history) + assert ctx.value.request_info.url.path == '/redirect/5' + assert ctx.value.request_info.method == 'GET' async def test_HTTP_200_GET_WITH_PARAMS(aiohttp_client):