Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/pengutronix/aiohttp-json-rpc
Implements JSON-RPC 2.0 using aiohttp
https://github.com/pengutronix/aiohttp-json-rpc
aiohttp django http-client http-server json-rpc python python-3-5 python-3-6
Last synced: about 2 months ago
JSON representation
Implements JSON-RPC 2.0 using aiohttp
- Host: GitHub
- URL: https://github.com/pengutronix/aiohttp-json-rpc
- Owner: pengutronix
- License: apache-2.0
- Archived: true
- Created: 2015-11-16T10:12:15.000Z (almost 9 years ago)
- Default Branch: master
- Last Pushed: 2021-09-26T17:37:02.000Z (about 3 years ago)
- Last Synced: 2024-09-25T19:25:07.142Z (about 2 months ago)
- Topics: aiohttp, django, http-client, http-server, json-rpc, python, python-3-5, python-3-6
- Language: Python
- Homepage:
- Size: 232 KB
- Stars: 55
- Watchers: 7
- Forks: 18
- Open Issues: 10
-
Metadata Files:
- Readme: README.rst
- License: LICENSE.txt
Awesome Lists containing this project
README
.. image:: https://img.shields.io/pypi/v/aiohttp-json-rpc.svg
:target: https://pypi.org/project/aiohttp-json-rpc.. image:: https://img.shields.io/travis/com/pengutronix/aiohttp-json-rpc/master.svg?label=Linux%20build%20%40%20Travis%20CI
:target: http://travis-ci.com/pengutronix/aiohttp-json-rpc.. image:: https://img.shields.io/pypi/pyversions/aiohttp-json-rpc.svg
aiohttp-json-rpc
================Implements `JSON-RPC 2.0 Specification `_ using `aiohttp `_
+---------------+---------------+
| Protocol | Support |
+===============+===============+
| Websocket | since v0.1 |
+---------------+---------------+
| POST | TODO |
+---------------+---------------+
| GET | TODO |
+---------------+---------------+Installation
------------.. code-block:: shell
pip install aiohttp-json-rpc
Usage
-----RPC methods can be added by using ``rpc.add_method()``.
All RPC methods are getting passed a ``aiohttp_json_rpc.communicaton.JsonRpcRequest``.
Server
~~~~~~The following code implements a simple RPC server that serves the method ``ping`` on ``localhost:8080``.
.. code-block:: python
from aiohttp.web import Application, run_app
from aiohttp_json_rpc import JsonRpc
import asyncioasync def ping(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... code-block:: html
var ws = new WebSocket("ws://localhost:8080");
var message_id = 0;ws.onmessage = function(event) {
console.log(JSON.parse(event.data));
}function ws_call_method(method, params) {
var request = {
jsonrpc: "2.0",
id: message_id,
method: method,
params: params
}ws.send(JSON.stringify(request));
message_id++;
}
These are example responses the server would give if you call ``ws_call_method``.
.. code-block:: html
--> 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:
.. code-block:: python
from aiohttp_json_rpc import JsonRpcClient
async def ping_json_rpc():
"""Connect to ws://localhost:8080/, call ping() and disconnect."""
rpc_client = JsonRpcClient()
try:
await rpc_client.connect('localhost', 8080)
call_result = await rpc_client.call('ping')
print(call_result) # prints 'pong' (if that's return val of ping)
finally:
await rpc_client.disconnect()asyncio.get_event_loop().run_until_complete(ping_json_rpc())
Or use asynchronous context manager interface:
.. code-block:: python
from aiohttp_json_rpc import JsonRpcClientContext
async def jrpc_coro():
async with JsonRpcClientContext('ws://localhost:8000/rpc') as jrpc:
# `some_other_method` will get request.params filled with `args` and
# `kwargs` keys:
method_res = await jrpc.some_other_method('arg1', key='arg2')
return method_res
asyncio.get_event_loop().run_until_complete(jrpc_coro())Features
--------Error Handling
~~~~~~~~~~~~~~All errors specified in the `error specification `_ but the InvalidParamsError are handled internally.
If your coroutine got called with wrong params you can raise an ``aiohttp_json_rpc.RpcInvalidParamsError`` instead of sending an error by yourself.
The JSONRPC protocol defines a range for server defined errors.
``aiohttp_json_rpc.RpcGenericServerDefinedError`` implements this feature... code-block:: python
from aiohttp_json_rpc import RpcInvalidParamsError
async def add(request):
try:
a = params.get('a')
b = params.get('b')return a + b
except KeyError:
raise RpcInvalidParamsErrorasync def add(request):
raise RpcGenericServerDefinedError(
error_code=-32050,
message='Computer says no.',
)Error Logging
~~~~~~~~~~~~~Every traceback caused by an RPC method will be caught and logged.
The RPC will send an RPC ServerError and proceed as if nothing happened.
.. code-block:: python
async def divide(request):
return 1 / 0 # will raise a ZeroDivisionError.. code-block::
ERROR: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 Subscribe
~~~~~~~~~~~~~~~~~Any client of an RPC object can subscribe to a topic using the built-in RPC method ``subscribe()``.
Topics can be added using ``rpc.add_topics``.
Authentication
~~~~~~~~~~~~~~The auth system works like in Django with decorators.
For details see the corresponding Django documentation.+--------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Decorator | Django Equivalent |
+==================================================+=======================================================================================================================================================================+
| aiohttp_json_rpc.django.auth.login_required | `django.contrib.auth.decorators.login_required `_ |
+--------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| aiohttp_json_rpc.django.auth.permission_required | `django.contrib.auth.decorators.permission_required `_ |
+--------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| aiohttp_json_rpc.django.auth.user_passes_test | `django.contrib.auth.decorators.user_passes_test `_ |
+--------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+.. code-block:: python
from aiohttp_json_rpc.auth import (
permission_required,
user_passes_test,
login_required,
)from aiohttp_json_rpc.auth.django import DjangoAuthBackend
from aiohttp_json_rpc import JsonRpc@login_required
@permission_required('ping')
@user_passes_test(lambda user: user.is_superuser)
async def ping(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 Connections
~~~~~~~~~~~~~~~~~~~~~
If 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 method ``ping`` on ``localhost:8080``
.. code-block:: python
from aiohttp.web import Application, run_app
from aiohttp_json_rpc import JsonRpc
import asyncio
import sslasync def ping(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 the ``JsonRpcClient`` Python client.
.. code-block:: python
from aiohttp_json_rpc import JsonRpcClient
import sslasync def ping_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:
await rpc_client.connect('localhost', 8080, ssl=ssl_context)
call_result = await rpc_client.call('ping')
print(call_result) # prints 'pong' (if that's return val of ping)
finally:
await rpc_client.disconnect()asyncio.get_event_loop().run_until_complete(ping_json_rpc())
See `aiohttp documentation `_ for more details on SSL control for TCP sockets.
Class References
----------------class aiohttp_json_rpc.JsonRpc(object)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Methods
'''''''``def 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.
Uses ``filter()``.
``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.