From 0d0f657240f154310960c6cd260b5545b8b3bdb4 Mon Sep 17 00:00:00 2001 From: Romuald Juchnowicz-Bierbasz Date: Fri, 5 Jul 2019 18:43:28 +0200 Subject: [PATCH] SDK-2930: Refactor http module --- src/galaxy/http.py | 88 +++++++++++++++++++++++++++++----------------- tests/test_http.py | 37 +++++++++++++++++++ 2 files changed, 92 insertions(+), 33 deletions(-) create mode 100644 tests/test_http.py diff --git a/src/galaxy/http.py b/src/galaxy/http.py index 667f55a..65cfd22 100644 --- a/src/galaxy/http.py +++ b/src/galaxy/http.py @@ -1,5 +1,6 @@ import asyncio import ssl +from contextlib import contextmanager from http import HTTPStatus import aiohttp @@ -13,43 +14,64 @@ from galaxy.api.errors import ( class HttpClient: + """Deprecated""" def __init__(self, limit=20, timeout=aiohttp.ClientTimeout(total=60), cookie_jar=None): - ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) - ssl_context.load_verify_locations(certifi.where()) - connector = aiohttp.TCPConnector(limit=limit, ssl=ssl_context) - self._session = aiohttp.ClientSession(connector=connector, timeout=timeout, cookie_jar=cookie_jar) + connector = create_tcp_connector(limit=limit) + self._session = create_client_session(connector=connector, timeout=timeout, cookie_jar=cookie_jar) async def close(self): await self._session.close() async def request(self, method, url, *args, **kwargs): - try: - response = await self._session.request(method, url, *args, **kwargs) - except asyncio.TimeoutError: - raise BackendTimeout() - except aiohttp.ServerDisconnectedError: - raise BackendNotAvailable() - except aiohttp.ClientConnectionError: - raise NetworkError() - except aiohttp.ContentTypeError: - raise UnknownBackendResponse() - except aiohttp.ClientError: - logging.exception( - "Caught exception while running {} request for {}".format(method, url)) - raise UnknownError() - if response.status == HTTPStatus.UNAUTHORIZED: - raise AuthenticationRequired() - if response.status == HTTPStatus.FORBIDDEN: - raise AccessDenied() - if response.status == HTTPStatus.SERVICE_UNAVAILABLE: - raise BackendNotAvailable() - if response.status == HTTPStatus.TOO_MANY_REQUESTS: - raise TooManyRequests() - if response.status >= 500: - raise BackendError() - if response.status >= 400: - logging.warning( - "Got status {} while running {} request for {}".format(response.status, method, url)) - raise UnknownError() + with handle_exception(): + return await self._session.request(method, url, *args, **kwargs) + + +def create_tcp_connector(*args, **kwargs): + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ssl_context.load_verify_locations(certifi.where()) + kwargs.setdefault("ssl", ssl_context) + kwargs.setdefault("limit", 20) + return aiohttp.TCPConnector(*args, **kwargs) + + +def create_client_session(*args, **kwargs): + kwargs.setdefault("connector", create_tcp_connector()) + kwargs.setdefault("timeout", aiohttp.ClientTimeout(total=60)) + kwargs.setdefault("raise_for_status", True) + return aiohttp.ClientSession(*args, **kwargs) + + +@contextmanager +def handle_exception(): + try: + yield + except asyncio.TimeoutError: + raise BackendTimeout() + except aiohttp.ServerDisconnectedError: + raise BackendNotAvailable() + except aiohttp.ClientConnectionError: + raise NetworkError() + except aiohttp.ContentTypeError: + raise UnknownBackendResponse() + except aiohttp.ClientResponseError as error: + if error.status == HTTPStatus.UNAUTHORIZED: + raise AuthenticationRequired() + if error.status == HTTPStatus.FORBIDDEN: + raise AccessDenied() + if error.status == HTTPStatus.SERVICE_UNAVAILABLE: + raise BackendNotAvailable() + if error.status == HTTPStatus.TOO_MANY_REQUESTS: + raise TooManyRequests() + if error.status >= 500: + raise BackendError() + if error.status >= 400: + logging.warning( + "Got status %d while performing %s request for %s", + error.status, error.request_info.method, str(error.request_info.url) + ) + raise UnknownError() + except aiohttp.ClientError: + logging.exception("Caught exception while performing request") + raise UnknownError() - return response diff --git a/tests/test_http.py b/tests/test_http.py new file mode 100644 index 0000000..b9f2a3e --- /dev/null +++ b/tests/test_http.py @@ -0,0 +1,37 @@ +import asyncio +from http import HTTPStatus + +import aiohttp +import pytest + +from galaxy.api.errors import ( + AccessDenied, AuthenticationRequired, BackendTimeout, BackendNotAvailable, BackendError, NetworkError, + TooManyRequests, UnknownBackendResponse, UnknownError +) +from galaxy.http import handle_exception + +request_info = aiohttp.RequestInfo("http://o.pl", "GET", {}) + +@pytest.mark.parametrize( + "aiohttp_exception,expected_exception_type", + [ + (asyncio.TimeoutError(), BackendTimeout), + (aiohttp.ServerDisconnectedError(), BackendNotAvailable), + (aiohttp.ClientConnectionError(), NetworkError), + (aiohttp.ContentTypeError(request_info, []), UnknownBackendResponse), + (aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.UNAUTHORIZED), AuthenticationRequired), + (aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.FORBIDDEN), AccessDenied), + (aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.SERVICE_UNAVAILABLE), BackendNotAvailable), + (aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.TOO_MANY_REQUESTS), TooManyRequests), + (aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.INTERNAL_SERVER_ERROR), BackendError), + (aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.NOT_IMPLEMENTED), BackendError), + (aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.BAD_REQUEST), UnknownError), + (aiohttp.ClientResponseError(request_info, [], status=HTTPStatus.NOT_FOUND), UnknownError), + (aiohttp.ClientError(), UnknownError) + ] +) +def test_handle_exception(aiohttp_exception, expected_exception_type): + with pytest.raises(expected_exception_type): + with handle_exception(): + raise aiohttp_exception +