aiohttp based Async Authenticated API Client

This commit is contained in:
hackrush 2018-08-11 15:55:36 +05:30 committed by Jack Robison
parent ab3b0134b5
commit 083bf4f61d
No known key found for this signature in database
GPG key ID: DF25C68FE0239BB2
2 changed files with 55 additions and 78 deletions

View file

@ -16,7 +16,7 @@ from lbrynet.core.system_info import get_platform
async def execute_command(method, params, conf_path=None): async def execute_command(method, params, conf_path=None):
# this check if the daemon is running or not # this check if the daemon is running or not
try: try:
api = LBRYAPIClient.get_client(conf_path) api = await LBRYAPIClient.get_client(conf_path)
await api.status() await api.status()
except (ClientConnectorError, ConnectionError): except (ClientConnectorError, ConnectionError):
print("Could not connect to daemon. Are you sure it's running?") print("Could not connect to daemon. Are you sure it's running?")
@ -25,6 +25,8 @@ async def execute_command(method, params, conf_path=None):
# this actually executes the method # this actually executes the method
try: try:
resp = await api.call(method, params) resp = await api.call(method, params)
if not api.session.closed:
await api.session.close()
print(json.dumps(resp["result"], indent=2)) print(json.dumps(resp["result"], indent=2))
except KeyError: except KeyError:
if resp["error"]["code"] == -32500: if resp["error"]["code"] == -32500:

View file

@ -1,11 +1,9 @@
# pylint: skip-file
import os import os
import json import json
import aiohttp import aiohttp
from urllib.parse import urlparse
import requests
from requests.cookies import RequestsCookieJar
import logging import logging
from urllib.parse import urlparse
from lbrynet import conf from lbrynet import conf
from lbrynet.daemon.auth.util import load_api_keys, APIKey, API_KEY_NAME, get_auth_message from lbrynet.daemon.auth.util import load_api_keys, APIKey, API_KEY_NAME, get_auth_message
@ -17,12 +15,6 @@ HTTP_TIMEOUT = 30
SCHEME = "http" SCHEME = "http"
def copy_cookies(cookies):
result = RequestsCookieJar()
result.update(cookies)
return result
class JSONRPCException(Exception): class JSONRPCException(Exception):
def __init__(self, rpc_error): def __init__(self, rpc_error):
super().__init__() super().__init__()
@ -42,7 +34,7 @@ class UnAuthAPIClient:
return f return f
@classmethod @classmethod
def from_url(cls, url): async def from_url(cls, url):
url_fragment = urlparse(url) url_fragment = urlparse(url)
host = url_fragment.hostname host = url_fragment.hostname
port = url_fragment.port port = url_fragment.port
@ -55,14 +47,14 @@ class UnAuthAPIClient:
return await resp.json() return await resp.json()
class AuthAPIClient: class AsyncAuthAPIClient:
def __init__(self, key, timeout, connection, count, cookies, url, login_url): def __init__(self, key, session, cookies, url, login_url):
self.session = session
self.__api_key = key self.__api_key = key
self.__service_url = login_url self.__login_url = login_url
self.__id_count = count self.__id_count = 0
self.__url = url self.__url = url
self.__conn = connection self.__cookies = cookies
self.__cookies = copy_cookies(cookies)
def __getattr__(self, name): def __getattr__(self, name):
if name.startswith('__') and name.endswith('__'): if name.startswith('__') and name.endswith('__'):
@ -76,6 +68,7 @@ class AuthAPIClient:
async def call(self, method, params=None): async def call(self, method, params=None):
params = params or {} params = params or {}
self.__id_count += 1 self.__id_count += 1
pre_auth_post_data = { pre_auth_post_data = {
'version': '2', 'version': '2',
'method': method, 'method': method,
@ -83,71 +76,53 @@ class AuthAPIClient:
'id': self.__id_count 'id': self.__id_count
} }
to_auth = get_auth_message(pre_auth_post_data) to_auth = get_auth_message(pre_auth_post_data)
pre_auth_post_data.update({'hmac': self.__api_key.get_hmac(to_auth).decode()}) auth_msg = self.__api_key.get_hmac(to_auth).decode()
pre_auth_post_data.update({'hmac': auth_msg})
post_data = json.dumps(pre_auth_post_data) post_data = json.dumps(pre_auth_post_data)
cookies = copy_cookies(self.__cookies)
req = requests.Request( headers = {
method='POST', url=self.__service_url, data=post_data, cookies=cookies, 'Host': self.__url.hostname,
headers={ 'User-Agent': USER_AGENT,
'Host': self.__url.hostname, 'Content-type': 'application/json'
'User-Agent': USER_AGENT, }
'Content-type': 'application/json'
} async with self.session.post(self.__login_url, data=post_data, headers=headers) as resp:
) if resp is None:
http_response = self.__conn.send(req.prepare()) raise JSONRPCException({'code': -342, 'message': 'missing HTTP response from server'})
if http_response is None: resp.raise_for_status()
raise JSONRPCException({'code': -342, 'message': 'missing HTTP response from server'})
http_response.raise_for_status() next_secret = resp.headers.get(LBRY_SECRET, False)
next_secret = http_response.headers.get(LBRY_SECRET, False) if next_secret:
if next_secret: self.__api_key.secret = next_secret
self.__api_key.secret = next_secret
self.__cookies = copy_cookies(http_response.cookies) return await resp.json()
return http_response.json()
@classmethod @classmethod
def config(cls, key_name=None, key=None, pw_path=None, timeout=HTTP_TIMEOUT, connection=None, count=0, async def get_client(cls, key_name=None):
cookies=None, auth=None, url=None, login_url=None):
api_key_name = key_name or API_KEY_NAME api_key_name = key_name or API_KEY_NAME
pw_path = os.path.join(conf.settings['data_dir'], ".api_keys") if not pw_path else pw_path
if not key:
keys = load_api_keys(pw_path)
api_key = keys.get(api_key_name, False)
else:
api_key = APIKey(name=api_key_name, secret=key)
if login_url is None:
service_url = "http://{}:{}@{}:{}".format(
api_key_name, api_key.secret, conf.settings['api_host'], conf.settings['api_port']
)
else:
service_url = login_url
id_count = count
if auth is None and connection is None and cookies is None and url is None: pw_path = os.path.join(conf.settings['data_dir'], ".api_keys")
# This is a new client instance, start an authenticated session keys = load_api_keys(pw_path)
url = urlparse(service_url) api_key = keys.get(api_key_name, False)
conn = requests.Session()
req = requests.Request( login_url = "http://{}:{}@{}:{}".format(api_key_name, api_key.secret, conf.settings['api_host'],
method='POST', url=service_url, headers={ conf.settings['api_port'])
'Host': url.hostname, url = urlparse(login_url)
'User-Agent': USER_AGENT,
'Content-type': 'application/json' headers = {
}) 'Host': url.hostname,
r = req.prepare() 'User-Agent': USER_AGENT,
http_response = conn.send(r) 'Content-type': 'application/json'
cookies = RequestsCookieJar() }
cookies.update(http_response.cookies)
uid = cookies.get(TWISTED_SESSION) session = aiohttp.ClientSession()
api_key = APIKey.new(seed=uid.encode())
else: async with session.post(login_url, headers=headers) as r:
# This is a client that already has a session, use it cookies = r.cookies
conn = connection
if not cookies.get(LBRY_SECRET): uid = cookies.get(TWISTED_SESSION).value
raise Exception("Missing cookie") api_key = APIKey.new(seed=uid.encode())
secret = cookies.get(LBRY_SECRET) return cls(api_key, session, cookies, url, login_url)
api_key = APIKey(secret, api_key_name)
return cls(api_key, timeout, conn, id_count, cookies, url, service_url)
class LBRYAPIClient: class LBRYAPIClient:
@ -156,5 +131,5 @@ class LBRYAPIClient:
conf.conf_file = conf_path conf.conf_file = conf_path
if not conf.settings: if not conf.settings:
conf.initialize_settings() conf.initialize_settings()
return AuthAPIClient.config() if conf.settings['use_auth_http'] else \ return AsyncAuthAPIClient.get_client() if conf.settings['use_auth_http'] else \
UnAuthAPIClient.from_url(conf.settings.get_api_connection_string()) UnAuthAPIClient.from_url(conf.settings.get_api_connection_string())