|
import json |
|
import socket |
|
import time |
|
from typing import Optional, Any, Mapping, Callable, Type, Tuple |
|
|
|
import requests |
|
from requests import HTTPError |
|
from urlobject import URLObject |
|
from urlobject.path import URLPath |
|
|
|
from .common import translate_dict_func |
|
|
|
|
|
def get_host_ip() -> Optional[str]: |
|
s = None |
|
try: |
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|
s.connect(('8.8.8.8', 80)) |
|
ip = s.getsockname()[0] |
|
finally: |
|
if s is not None: |
|
s.close() |
|
return ip |
|
|
|
|
|
_DEFAULT_HTTP_PORT = 80 |
|
_DEFAULT_HTTPS_PORT = 443 |
|
|
|
|
|
def split_http_address(address: str, default_port: Optional[int] = None) -> Tuple[str, int, bool, str]: |
|
_url = URLObject(address) |
|
|
|
_host = _url.hostname |
|
_https = (_url.scheme.lower()) == 'https' |
|
_port = _url.port or default_port or (_DEFAULT_HTTPS_PORT if _https else _DEFAULT_HTTP_PORT) |
|
_path = str(_url.path) or '' |
|
|
|
return _host, _port, _https, _path |
|
|
|
|
|
DEFAULT_RETRIES = 5 |
|
DEFAULT_RETRY_WAITING = 1.0 |
|
|
|
|
|
class HttpEngine: |
|
|
|
def __init__(self, host: str, port: int, https: bool = False, path: str = None): |
|
self.__base_url = URLObject().with_scheme('https' if https else 'http') \ |
|
.with_hostname(host).with_port(port).add_path(path or '') |
|
self.__session = requests.session() |
|
self.__session.trust_env = False |
|
|
|
|
|
def _data_process(self, data: Optional[Mapping[str, Any]] = None) -> Mapping[str, Any]: |
|
return data or {} |
|
|
|
|
|
def _base_headers(self) -> Mapping[str, None]: |
|
return {} |
|
|
|
def _error_handler(self, err: Exception): |
|
raise err |
|
|
|
def get_url(self, path: str = None): |
|
original_segments = self.__base_url.path.segments |
|
path_segments = URLPath().add(path or '').segments |
|
return str(self.__base_url.with_path(URLPath.join_segments(original_segments + path_segments))) |
|
|
|
def __single_request( |
|
self, |
|
method: str, |
|
path: str, |
|
data: Optional[Mapping[str, Any]] = None, |
|
headers: Optional[Mapping[str, Any]] = None, |
|
params: Optional[Mapping[str, Any]] = None, |
|
raise_for_status: bool = True |
|
): |
|
response = self.__session.request( |
|
method=method, |
|
url=self.get_url(path), |
|
data=json.dumps(self._data_process(data) or {}), |
|
headers=headers, |
|
params=params or {}, |
|
) |
|
if raise_for_status: |
|
response.raise_for_status() |
|
|
|
return response |
|
|
|
def request( |
|
self, |
|
method: str, |
|
path: str, |
|
data: Optional[Mapping[str, Any]] = None, |
|
headers: Optional[Mapping[str, Any]] = None, |
|
params: Optional[Mapping[str, Any]] = None, |
|
raise_for_status: bool = True, |
|
retries: Optional[int] = None, |
|
retry_waiting: Optional[float] = None, |
|
) -> requests.Response: |
|
_headers = dict(self._base_headers()) |
|
_headers.update(headers or {}) |
|
|
|
retries = retries or DEFAULT_RETRIES |
|
retry_waiting = retry_waiting or DEFAULT_RETRY_WAITING |
|
|
|
try: |
|
_current_retries = 0 |
|
while True: |
|
try: |
|
response = self.__single_request(method, path, data, _headers, params, raise_for_status) |
|
except requests.exceptions.HTTPError as err: |
|
raise err |
|
except requests.exceptions.RequestException as err: |
|
_current_retries += 1 |
|
if _current_retries > retries: |
|
raise err |
|
else: |
|
time.sleep(retry_waiting) |
|
else: |
|
break |
|
except Exception as e: |
|
self._error_handler(e) |
|
else: |
|
return response |
|
|
|
|
|
def get_http_engine_class( |
|
headers: Mapping[str, Callable[..., Any]], |
|
data_processor: Optional[Callable[[Mapping[str, Any]], Mapping[str, Any]]] = None, |
|
http_error_gene: Optional[Callable[[HTTPError], Exception]] = None, |
|
) -> Callable[..., Type[HttpEngine]]: |
|
|
|
def _func(*args, **kwargs) -> Type[HttpEngine]: |
|
|
|
class _HttpEngine(HttpEngine): |
|
|
|
def _data_process(self, data: Optional[Mapping[str, Any]] = None) -> Mapping[str, Any]: |
|
return (data_processor or (lambda d: d or {}))(data or {}) |
|
|
|
def _base_headers(self) -> Mapping[str, None]: |
|
return translate_dict_func(headers)(*args, **kwargs) |
|
|
|
def _error_handler(self, err: Exception): |
|
if http_error_gene is not None and isinstance(err, HTTPError): |
|
raise http_error_gene(err) |
|
else: |
|
raise err |
|
|
|
return _HttpEngine |
|
|
|
return _func |
|
|