Implementation JSON-RPC 2.0 server and client using aiohttp on top of websockets transport
Project description
aiohttp-json-rpc
Implements JSON-RPC 2.0 Specification using aiohttp
Protocol |
Support |
---|---|
Websocket |
since v0.1 |
POST |
TODO |
GET |
TODO |
Installation
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.
from aiohttp.web import Application
from aiohttp_json_rpc import JsonRpc
import asyncio
@asyncio.coroutine
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)
handler = app.make_handler()
server = loop.run_until_complete(
loop.create_server(handler, '0.0.0.0', 8080))
loop.run_forever()
Client (JS)
The following code implements a simple RPC client that connects to the server above and prints all incoming messages to the console.
<script src="//code.jquery.com/jquery-2.2.1.js"></script>
<script>
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++;
}
</script>
These are example responses the server would give if you call ws_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:
from aiohttp_json_rpc import JsonRpcClient
async def ping_json_rpc():
"""Connect to ws://localhost:8080/rpc, call ping() and disconnect."""
rpc_client = JsonRpcClient()
try:
await rpc_client.connect('localhost', 8080, '/rpc')
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:
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.
from aiohttp_json_rpc import RpcInvalidParamsError
@asyncio.coroutine
def add(request):
try:
a = params.get('a')
b = params.get('b')
return a + b
except KeyError:
raise RpcInvalidParamsError
@asyncio.coroutine
async 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.
@asyncio.coroutine
def divide(request):
return 1 / 0 # will raise a ZeroDivisionError
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 zero
Publish 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 |
|
aiohttp_json_rpc.django.auth.permission_required |
|
aiohttp_json_rpc.django.auth.user_passes_test |
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)
@asyncio.coroutine
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')])
)
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.
- 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for aiohttp_json_rpc-0.10-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ebe74d620604a559c33537d2ce9a0869a7efe562e713a8c565974c9ae1e92cd7 |
|
MD5 | 31754e0a39af99f6cb62bfcb11afb4a0 |
|
BLAKE2b-256 | 71f6433dde3ab272bc12c77520f4ea26c8649d25aa11dbb3f8e42944f1545b49 |