lbry-sdk/lbry/service/daemon.py

181 lines
6.2 KiB
Python
Raw Normal View History

2020-05-01 15:33:58 +02:00
import json
2020-08-20 16:43:44 +02:00
import signal
2020-05-01 15:33:58 +02:00
import asyncio
import logging
2020-08-20 16:43:44 +02:00
2020-05-01 15:33:58 +02:00
from weakref import WeakSet
2020-08-20 16:43:44 +02:00
from asyncio.runners import _cancel_all_tasks
2020-05-01 15:33:58 +02:00
from aiohttp.web import Application, AppRunner, WebSocketResponse, TCPSite, Response
from aiohttp.http_websocket import WSMsgType, WSCloseCode
2020-06-05 06:35:22 +02:00
from lbry.service.json_encoder import JSONResponseEncoder
2020-05-01 15:33:58 +02:00
from lbry.service.base import Service
from lbry.service.api import API
2020-05-21 00:05:13 +02:00
from lbry.console import Console
2020-05-01 15:33:58 +02:00
2020-08-20 16:43:44 +02:00
log = logging.getLogger(__name__)
2020-05-01 15:33:58 +02:00
def jsonrpc_dumps_pretty(obj, **kwargs):
2020-06-05 06:35:22 +02:00
#if not isinstance(obj, dict):
# data = {"jsonrpc": "2.0", "error": obj.to_dict()}
#else:
data = {"jsonrpc": "2.0", "result": obj}
2020-05-01 15:33:58 +02:00
return json.dumps(data, cls=JSONResponseEncoder, sort_keys=True, indent=2, **kwargs) + "\n"
class WebSocketLogHandler(logging.Handler):
def __init__(self, send_message):
super().__init__()
self.send_message = send_message
def emit(self, record):
try:
self.send_message({
'type': 'log',
'name': record.name,
'message': self.format(record)
})
2020-06-05 06:35:22 +02:00
except Exception:
2020-05-01 15:33:58 +02:00
self.handleError(record)
class WebSocketManager(WebSocketResponse):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def subscribe(self, requested: list, subscriptions):
2020-06-05 06:35:22 +02:00
for request in requested:
if request == '*':
2020-05-01 15:33:58 +02:00
for _, component in subscriptions.items():
for _, sockets in component.items():
sockets.add(self)
2020-06-05 06:35:22 +02:00
elif '.' not in request:
for _, sockets in subscriptions[request].items():
2020-05-01 15:33:58 +02:00
sockets.add(self)
2020-06-05 06:35:22 +02:00
elif request.count('.') == 1:
component, stream = request.split('.')
2020-05-01 15:33:58 +02:00
subscriptions[component][stream].add(self)
def unsubscribe(self, subscriptions):
for _, component in subscriptions.items():
for _, sockets in component.items():
sockets.discard(self)
class Daemon:
2020-05-21 00:05:13 +02:00
def __init__(self, service: Service, console: Console):
2020-05-01 15:33:58 +02:00
self.service = service
self.conf = service.conf
2020-05-21 00:05:13 +02:00
self.console = console
2020-05-01 15:33:58 +02:00
self.api = API(service)
self.app = Application()
self.app['websockets'] = WeakSet()
self.app['subscriptions'] = {}
2020-06-05 06:35:22 +02:00
self.components = {}
2020-05-01 15:33:58 +02:00
#for component in components:
# streams = self.app['subscriptions'][component.name] = {}
# for event_name, event_stream in component.event_streams.items():
# streams[event_name] = WeakSet()
# event_stream.listen(partial(self.broadcast_event, component.name, event_name))
self.app.router.add_get('/ws', self.on_connect)
2020-07-21 05:48:27 +02:00
self.app.router.add_post('/api', self.on_rpc)
2020-05-01 15:33:58 +02:00
self.app.on_shutdown.append(self.on_shutdown)
self.runner = AppRunner(self.app)
2020-05-21 00:05:13 +02:00
def run(self):
2020-08-20 16:43:44 +02:00
loop = asyncio.new_event_loop()
for sig in (signal.SIGHUP, signal.SIGTERM, signal.SIGINT):
loop.add_signal_handler(sig, loop.stop)
2020-05-21 00:05:13 +02:00
try:
loop.run_until_complete(self.start())
loop.run_forever()
finally:
2020-08-20 16:43:44 +02:00
try:
loop.run_until_complete(self.stop())
finally:
try:
_cancel_all_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens())
finally:
loop.close()
2020-05-21 00:05:13 +02:00
2020-05-01 15:33:58 +02:00
async def start(self):
2020-05-21 00:05:13 +02:00
self.console.starting()
2020-05-01 15:33:58 +02:00
await self.runner.setup()
site = TCPSite(self.runner, self.conf.api_host, self.conf.api_port)
2020-05-01 15:33:58 +02:00
await site.start()
await self.service.start()
async def stop(self):
await self.service.stop()
await self.runner.cleanup()
async def on_rpc(self, request):
data = await request.json()
params = data.get('params', {})
method = getattr(self.api, data['method'])
result = await method(**params)
encoded_result = jsonrpc_dumps_pretty(result, service=self.service)
return Response(
text=encoded_result,
content_type='application/json'
)
async def on_connect(self, request):
web_socket = WebSocketManager()
await web_socket.prepare(request)
self.app['websockets'].add(web_socket)
try:
async for msg in web_socket:
if msg.type == WSMsgType.TEXT:
asyncio.create_task(self.on_message(web_socket, msg.json()))
elif msg.type == WSMsgType.ERROR:
print('web socket connection closed with exception %s' %
web_socket.exception())
finally:
web_socket.unsubscribe(self.app['subscriptions'])
self.app['websockets'].discard(web_socket)
return web_socket
async def on_message(self, web_socket: WebSocketManager, msg: dict):
2020-07-07 16:52:41 +02:00
if msg['method'] == 'subscribe':
2020-05-01 15:33:58 +02:00
streams = msg['streams']
if isinstance(streams, str):
streams = [streams]
web_socket.subscribe(streams, self.app['subscriptions'])
2020-07-07 16:52:41 +02:00
else:
params = msg.get('params', {})
method = getattr(self.api, msg['method'])
result = await method(**params)
encoded_result = jsonrpc_dumps_pretty(result, service=self.service)
2020-05-01 15:33:58 +02:00
await web_socket.send_json({
2020-07-07 16:52:41 +02:00
'id': msg.get('id', ''),
'result': encoded_result
2020-05-01 15:33:58 +02:00
})
@staticmethod
async def on_shutdown(app):
for web_socket in set(app['websockets']):
await web_socket.close(code=WSCloseCode.GOING_AWAY, message='Server shutdown')
def broadcast_event(self, module, stream, payload):
for web_socket in self.app['subscriptions'][module][stream]:
asyncio.create_task(web_socket.send_json({
'module': module,
'stream': stream,
'payload': payload
}))
def broadcast_message(self, msg):
for web_socket in self.app['websockets']:
asyncio.create_task(web_socket.send_json({
'module': 'blockchain_sync',
'payload': msg
}))