Package: aiohttp-json-rpc
aiohttp-json-rpcImplementsJSON-RPC 2.0 SpecificationusingaiohttpProtocolSupportWebsocketsince v0.1POSTTODOGETTODOInstallationpipinstallaiohttp-json-rpcUsageRPC methods can be added by usingrpc.add_method().All RPC methods are getting passed aaiohttp_json_rpc.communicaton.JsonRpcRequest.ServerThe following code implements a simple RPC server that serves the methodpingonlocalhost:8080.fromaiohttp.webimportApplication,run_appfromaiohttp_json_rpcimportJsonRpcimportasyncioasyncdefping(request):return'pong'if__name__=='__main__':loop=asyncio.get_event_loop()rpc=JsonRpc()rpc.add_methods(('',ping),)app=Application(loop=loop)app.router.add_route('*','/',rpc.handle_request)run_app(app,host='0.0.0.0',port=8080)Client (JS)The following code implements a simple RPC client that connects to the server above
and prints all incoming messages to the console.<scriptsrc="//code.jquery.com/jquery-2.2.1.js"></script><script>varws=newWebSocket("ws://localhost:8080");varmessage_id=0;ws.onmessage=function(event){console.log(JSON.parse(event.data));}functionws_call_method(method,params){varrequest={jsonrpc:"2.0",id:message_id,method:method,params:params}ws.send(JSON.stringify(request));message_id++;}</script>These are example responses the server would give if you callws_call_method.--> ws_call_method("get_methods")<--{"jsonrpc":"2.0","result":["get_methods","ping"],"id":1}-->ws_call_method("ping")<--{"jsonrpc":"2.0","method":"ping","params":"pong","id":2}Client (Python)There’s also Python client, which can be used as follows:fromaiohttp_json_rpcimportJsonRpcClientasyncdefping_json_rpc():"""Connect to ws://localhost:8080/, call ping() and disconnect."""rpc_client=JsonRpcClient()try:awaitrpc_client.connect('localhost',8080)call_result=awaitrpc_client.call('ping')print(call_result)# prints 'pong' (if that's return val of ping)finally:awaitrpc_client.disconnect()asyncio.get_event_loop().run_until_complete(ping_json_rpc())Or use asynchronous context manager interface:fromaiohttp_json_rpcimportJsonRpcClientContextasyncdefjrpc_coro():asyncwithJsonRpcClientContext('ws://localhost:8000/rpc')asjrpc:# `some_other_method` will get request.params filled with `args` and# `kwargs` keys:method_res=awaitjrpc.some_other_method('arg1',key='arg2')returnmethod_resasyncio.get_event_loop().run_until_complete(jrpc_coro())FeaturesError HandlingAll errors specified in theerror specificationbut the InvalidParamsError are handled internally.If your coroutine got called with wrong params you can raise anaiohttp_json_rpc.RpcInvalidParamsErrorinstead of sending an error by yourself.The JSONRPC protocol defines a range for server defined errors.aiohttp_json_rpc.RpcGenericServerDefinedErrorimplements this feature.fromaiohttp_json_rpcimportRpcInvalidParamsErrorasyncdefadd(request):try:a=params.get('a')b=params.get('b')returna+bexceptKeyError:raiseRpcInvalidParamsErrorasyncdefadd(request):raiseRpcGenericServerDefinedError(error_code=-32050,message='Computer says no.',)Error LoggingEvery traceback caused by an RPC method will be caught and logged.The RPC will send an RPC ServerError and proceed as if nothing happened.asyncdefdivide(request):return1/0# will raise a ZeroDivisionErrorERROR:JsonRpc: Traceback (most recent call last):
ERROR:JsonRpc:   File "aiohttp_json_rpc/base.py", line 289, in handle_websocket_request
ERROR:JsonRpc:     rsp = yield from methods[msg['method']](ws, msg)
ERROR:JsonRpc:   File "./example.py", line 12, in divide
ERROR:JsonRpc:     return 1 / 0
ERROR:JsonRpc: ZeroDivisionError: division by zeroPublish SubscribeAny client of an RPC object can subscribe to a topic using the built-in RPC methodsubscribe().Topics can be added usingrpc.add_topics.AuthenticationThe auth system works like in Django with decorators.
For details see the corresponding Django documentation.DecoratorDjango Equivalentaiohttp_json_rpc.django.auth.login_requireddjango.contrib.auth.decorators.login_requiredaiohttp_json_rpc.django.auth.permission_requireddjango.contrib.auth.decorators.permission_requiredaiohttp_json_rpc.django.auth.user_passes_testdjango.contrib.auth.decorators.user_passes_testfromaiohttp_json_rpc.authimport(permission_required,user_passes_test,login_required,)fromaiohttp_json_rpc.auth.djangoimportDjangoAuthBackendfromaiohttp_json_rpcimportJsonRpc@login_required@permission_required('ping')@user_passes_test(lambdauser:user.is_superuser)asyncdefping(request):return'pong'if__name__=='__main__':rpc=JsonRpc(auth_backend=DjangoAuthBackend())rpc.add_methods(('',ping),)rpc.add_topics(('foo',[login_required,permission_required('foo')]))Using SSL ConnectionsIf you need to setup a secure RPC server (use own certification files for example) you can create a ssl.SSLContext instance and pass it into the aiohttp web application.The following code implements a simple secure RPC server that serves the methodpingonlocalhost:8080fromaiohttp.webimportApplication,run_appfromaiohttp_json_rpcimportJsonRpcimportasyncioimportsslasyncdefping(request):return'pong'if__name__=='__main__':loop=asyncio.get_event_loop()rpc=JsonRpc()rpc.add_methods(('',ping),)app=Application(loop=loop)app.router.add_route('*','/',rpc.handle_request)ssl_context=ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)ssl_context.load_cert_chain('path/to/server.crt','path/to/server.key')run_app(app,host='0.0.0.0',port=8080,ssl_context=ssl_context)The following code implements a secure RPC client using theJsonRpcClientPython client.fromaiohttp_json_rpcimportJsonRpcClientimportsslasyncdefping_json_rpc():"""Connect to wss://localhost:8080/, call ping() and disconnect."""rpc_client=JsonRpcClient()ssl_context=ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)ssl_context.load_cert_chain('path/to/server.crt','path/to/server.key')try:awaitrpc_client.connect('localhost',8080,ssl=ssl_context)call_result=awaitrpc_client.call('ping')print(call_result)# prints 'pong' (if that's return val of ping)finally:awaitrpc_client.disconnect()asyncio.get_event_loop().run_until_complete(ping_json_rpc())Seeaiohttp documentationfor more details on SSL control for TCP sockets.Class Referencesclass aiohttp_json_rpc.JsonRpc(object)Methodsdef add_methods(self, *args,prefix='')Args have to be tuple containing a prefix as string (may be empty) and a module,
object, coroutine or import string.If second arg is module or object all coroutines in it are getting added.async def get_methods()Returns list of all available RPC methods.def filter(self, topics)Returns generator over all clients that have subscribed for given topic.Topics can be string or a list of strings.async def notify(self, topic, data)Send RPC notification to all connected clients subscribed to given topic.Data has to be JSON serializable.Usesfilter().async def subscribe(topics)Subscribe to a topic.Topics can be string or a list of strings.async def unsubscribe(topics)Unsubscribe from a topic.Topics can be string or a list of strings.async def get_topics()Get subscribable  topics as list of strings.