http: Add client based on requests module
This commit is contained in:
parent
c760647268
commit
1dc8d9162f
216
IzzyLib/http.py
216
IzzyLib/http.py
|
@ -12,6 +12,13 @@ from urllib.request import Request, urlopen
|
||||||
|
|
||||||
from . import error, __version__
|
from . import error, __version__
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
except ImportError:
|
||||||
|
logging.verbose('Requests module not found. RequestsClient disabled')
|
||||||
|
requests = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from Crypto.Hash import SHA256
|
from Crypto.Hash import SHA256
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
|
@ -35,6 +42,7 @@ except ImportError:
|
||||||
|
|
||||||
|
|
||||||
Client = None
|
Client = None
|
||||||
|
methods = ['connect', 'delete', 'get', 'head', 'options', 'patch', 'post', 'put', 'trace']
|
||||||
|
|
||||||
|
|
||||||
class HttpClient(object):
|
class HttpClient(object):
|
||||||
|
@ -107,7 +115,7 @@ class HttpClient(object):
|
||||||
request = Request(url, data=data, headers=parsed_headers, method=method)
|
request = Request(url, data=data, headers=parsed_headers, method=method)
|
||||||
|
|
||||||
if self.proxy.enabled:
|
if self.proxy.enabled:
|
||||||
request.set_proxy(f'{self.proxy.host}:{self.proxy.host}', self.proxy.ptype)
|
request.set_proxy(f'{self.proxy.host}:{self.proxy.port}', self.proxy.ptype)
|
||||||
|
|
||||||
return request
|
return request
|
||||||
|
|
||||||
|
@ -223,6 +231,212 @@ class HttpResponse(object):
|
||||||
return json.dumps(self.json().asDict(), indent=indent)
|
return json.dumps(self.json().asDict(), indent=indent)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestsClient(object):
|
||||||
|
def __init__(self, headers={}, useragent=f'IzzyLib/{__version__}', appagent=None, proxy_type='https', proxy_host=None, proxy_port=None):
|
||||||
|
proxy_ports = {
|
||||||
|
'http': 80,
|
||||||
|
'https': 443
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxy_type not in ['http', 'https']:
|
||||||
|
raise ValueError(f'Not a valid proxy type: {proxy_type}')
|
||||||
|
|
||||||
|
self.headers=headers
|
||||||
|
self.agent = f'{useragent} ({appagent})' if appagent else useragent
|
||||||
|
self.proxy = DotDict({
|
||||||
|
'enabled': True if proxy_host else False,
|
||||||
|
'ptype': proxy_type,
|
||||||
|
'host': proxy_host,
|
||||||
|
'port': proxy_ports[proxy_type] if not proxy_port else proxy_port
|
||||||
|
})
|
||||||
|
|
||||||
|
self.SetGlobal = SetClient
|
||||||
|
|
||||||
|
|
||||||
|
def __sign_request(self, request, privkey, keyid):
|
||||||
|
if not crypto_enabled:
|
||||||
|
logging.error('Crypto functions disabled')
|
||||||
|
return
|
||||||
|
|
||||||
|
request.add_header('(request-target)', f'{request.method.lower()} {request.path}')
|
||||||
|
request.add_header('host', request.host)
|
||||||
|
request.add_header('date', datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT'))
|
||||||
|
|
||||||
|
if request.body:
|
||||||
|
body_hash = b64encode(SHA256.new(request.body).digest()).decode("UTF-8")
|
||||||
|
request.add_header('digest', f'SHA-256={body_hash}')
|
||||||
|
request.add_header('content-length', len(request.body))
|
||||||
|
|
||||||
|
sig = {
|
||||||
|
'keyId': keyid,
|
||||||
|
'algorithm': 'rsa-sha256',
|
||||||
|
'headers': ' '.join([k.lower() for k in request.headers.keys()]),
|
||||||
|
'signature': b64encode(PkcsHeaders(privkey, request.headers)).decode('UTF-8')
|
||||||
|
}
|
||||||
|
|
||||||
|
sig_items = [f'{k}="{v}"' for k,v in sig.items()]
|
||||||
|
sig_string = ','.join(sig_items)
|
||||||
|
|
||||||
|
request.add_header('signature', sig_string)
|
||||||
|
|
||||||
|
request.remove_header('(request-target)')
|
||||||
|
request.remove_header('host')
|
||||||
|
|
||||||
|
|
||||||
|
def request(self, *args, method='get', **kwargs):
|
||||||
|
if method.lower() not in methods:
|
||||||
|
raise ValueError(f'Invalid method: {method}')
|
||||||
|
|
||||||
|
request = RequestsRequest(self, *args, method=method.lower(), **kwargs)
|
||||||
|
return RequestsResponse(request.send())
|
||||||
|
|
||||||
|
|
||||||
|
def file(self, url, filepath, *args, filename=None, **kwargs):
|
||||||
|
resp = self.request(url, *args, **kwargs)
|
||||||
|
|
||||||
|
if resp.status != 200:
|
||||||
|
logging.error(f'Failed to download {url}:', resp.status, resp.body)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return resp.save(filepath)
|
||||||
|
|
||||||
|
|
||||||
|
def image(self, url, filepath, *args, filename=None, ext='png', dimensions=(50, 50), **kwargs):
|
||||||
|
if not Image:
|
||||||
|
logging.error('Pillow module is not installed')
|
||||||
|
return
|
||||||
|
|
||||||
|
resp = self.request(url, *args, **kwargs)
|
||||||
|
|
||||||
|
if resp.status != 200:
|
||||||
|
logging.error(f'Failed to download {url}:', resp.status, resp.body)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not filename:
|
||||||
|
filename = Path(url).stem()
|
||||||
|
|
||||||
|
path = Path(filepath)
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
logging.error('Path does not exist:', path)
|
||||||
|
return False
|
||||||
|
|
||||||
|
byte = BytesIO()
|
||||||
|
image = Image.open(BytesIO(resp.body))
|
||||||
|
image.thumbnail(dimensions)
|
||||||
|
image.save(byte, format=ext.upper())
|
||||||
|
|
||||||
|
with path.join(filename).open('wb') as fd:
|
||||||
|
fd.write(byte.getvalue())
|
||||||
|
|
||||||
|
|
||||||
|
def json(self, *args, **kwargs):
|
||||||
|
return self.dict(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def dict(self, *args, headers={}, activity=True, **kwargs):
|
||||||
|
json_type = 'activity+json' if activity else 'json'
|
||||||
|
headers.update({
|
||||||
|
'accept': f'application/{json_type}'
|
||||||
|
})
|
||||||
|
return self.request(*args, headers=headers, **kwargs).dict
|
||||||
|
|
||||||
|
|
||||||
|
def signed_request(self, privkey, keyid, *args, **kwargs):
|
||||||
|
request = RequestsRequest(self, *args, **kwargs)
|
||||||
|
self.__sign_request(request, privkey, keyid)
|
||||||
|
return RequestsResponse(request.send())
|
||||||
|
|
||||||
|
|
||||||
|
class RequestsRequest(object):
|
||||||
|
def __init__(self, client, url, data=None, headers={}, query={}, method='get'):
|
||||||
|
self.args = [url]
|
||||||
|
self.kwargs = {'params': query}
|
||||||
|
self.method = method.lower()
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
new_headers = client.headers.copy()
|
||||||
|
new_headers.update(headers)
|
||||||
|
|
||||||
|
parsed_headers = {k.lower(): v for k,v in new_headers.items()}
|
||||||
|
|
||||||
|
if not parsed_headers.get('user-agent'):
|
||||||
|
parsed_headers['user-agent'] = client.agent
|
||||||
|
|
||||||
|
self.kwargs['headers'] = new_headers
|
||||||
|
self.kwargs['data'] = data
|
||||||
|
|
||||||
|
if client.proxy.enabled:
|
||||||
|
self.kwargs['proxies'] = {self.proxy.ptype: f'{self.proxy.ptype}://{self.proxy.host}:{self.proxy.port}'}
|
||||||
|
|
||||||
|
|
||||||
|
def send(self):
|
||||||
|
func = getattr(requests, self.method)
|
||||||
|
return func(*self.args, **self.kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestsResponse(object):
|
||||||
|
def __init__(self, response):
|
||||||
|
self.response = response
|
||||||
|
self.data = b''
|
||||||
|
self.headers = DefaultDict({k.lower(): v.lower() for k,v in response.headers.items()})
|
||||||
|
self.status = response.status_code
|
||||||
|
self.url = response.url
|
||||||
|
|
||||||
|
|
||||||
|
def chunks(self, size=256):
|
||||||
|
return self.response.iter_content(chunk_size=256)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def body(self):
|
||||||
|
for chunk in self.chunks():
|
||||||
|
self.data += chunk
|
||||||
|
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self):
|
||||||
|
if not self.data:
|
||||||
|
return self.body.decode(self.response.encoding)
|
||||||
|
|
||||||
|
return self.data.decode(self.response.encoding)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dict(self):
|
||||||
|
try:
|
||||||
|
return DotDict(self.text)
|
||||||
|
except Exception as e:
|
||||||
|
return DotDict()
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def json(self):
|
||||||
|
return json.dumps(self.dict)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def json_pretty(self, indent=4):
|
||||||
|
return json.dumps(self.dict, indent=indent)
|
||||||
|
|
||||||
|
|
||||||
|
def save(self, path, overwrite=True):
|
||||||
|
path = Path(path)
|
||||||
|
parent = path.parent()
|
||||||
|
|
||||||
|
if not parent.exists():
|
||||||
|
raise ValueError(f'Path does not exist: {parent}')
|
||||||
|
|
||||||
|
if overwrite and path.exists():
|
||||||
|
path.delete()
|
||||||
|
|
||||||
|
with path.open('wb') as fd:
|
||||||
|
for chunk in self.chunks():
|
||||||
|
fd.write(chunk)
|
||||||
|
|
||||||
|
|
||||||
def VerifyRequest(request: SanicRequest, actor: dict):
|
def VerifyRequest(request: SanicRequest, actor: dict):
|
||||||
'''Verify a header signature from a sanic request
|
'''Verify a header signature from a sanic request
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue