Server / Namespaces
Server
The RpcServer class is the central component that handles remote procedure calls.
At least one instance must be created.
A server instance expose a view that must be added to the Django routing system
from django.urls import path
from myapp.rpc import server
urlpatterns = [
# ... other url patterns
path('rpc/', server.view), # Synchronous view
]
Then, all requests to http://yourwebsite/rpc/ will be routed to the server’s view. They will be inspected and
parsed to be interpreted as RPC calls.
If a POST request have a correct Content-Type header and a well formed body, it will be handled by the right XML-RPC or JSON-RPC backend. The result of procedure calls will be encapsulated into a well-formed response, according to the request protocol (XML or JSON-RPC)
Note
You are free to choose the path that will be used to handle your remote procedure calls, but /rpc or
/RPC2 are the moste commonly used paths (example in xmlrpc.server docs)
Protocol restriction
Using the supported_protocol argument, you can configure a given server to handle only JSON-RPC or XML-RPC
requests. This can be used to set up protocol-specific servers.
Default: supported_protocol = Protocol.ALL
from modernrpc import Protocol, RpcServer
# Create protocol-specific servers
json_server = RpcServer(supported_protocol=Protocol.JSON_RPC)
xml_server = RpcServer(supported_protocol=Protocol.XML_RPC)
System procedures
By default, 3 introspection procedures (+ 1 multicall procedure, only for XML-RPC requests) are registered by a server,
under the namespace system. See Introspection procedures for history and protocol specific details, as
well as Introspection procedures & multicall for JSON-RPC specific implementation.
-
modernrpc.system_procedures.__system_list_methods(_ctx)[source]
Returns a list of all procedures exposed by the server
- Parameters:
_ctx (RpcRequestContext)
-
modernrpc.system_procedures.__system_method_signature(method_name, _ctx)[source]
Returns an array describing the possible signatures for the given procedure.
Currently, this procedure only returns one possible signature, so the result is a list of 1 list.
The inner list contains:
Return type as first element
Types of arguments from element 1 to N
- Parameters:
method_name (str) – Name of the procedure
_ctx (RpcRequestContext) – Request context for this call
- Returns:
An array of arrays describing types of return values and method arguments
-
modernrpc.system_procedures.__system_method_help(method_name, _ctx)[source]
Returns the documentation of the given procedure.
- Parameters:
method_name (str) – Name of the procedure
_ctx (RpcRequestContext) – Request context for this call
- Returns:
Documentation text for the procedure
-
modernrpc.system_procedures.__system_multicall(calls, _ctx)[source]
Call multiple procedure at once.
- Parameters:
calls (list) – An array of struct like {“methodName”: string, “params”: […, …]}
_ctx (RpcRequestContext) – Request context for this call
- Returns:
An array containing the result of each procedure call
Note
system.multicall builtin method is registered as synchronous version by default. In this version, each
procedure is executed sequentially. If you want to opt-in for the asynchronous version (procedures executed
concurently using asyncio.gather(), set settings.MODERNRPC_XMLRPC_ASYNC_MULTICALL = True.
Using the register_system_procedures argument, you can completely disable their automatic registration.
Default: register_system_procedures = True
from modernrpc.server import RpcServer
server = RpcServer(register_system_procedures=False)
# server won't register the system.* procedures
Warning
Disabling the system procedure registration may prevent some clients (in particular, XML-RPC ones) to send request to your server, use at your own risk.
Authentication
from myapp.auth import my_auth_callback
from modernrpc.server import RpcServer
# Create a server with authentication
server = RpcServer(auth=my_auth_callback)
@server.register_procedure
def multiply(a, b):
return a * b
All procedures registered in the server will use the auth callback configured in the server.
Note
Configured authentication callback can be overridden at namespace or procedure level.
For more information about authentication, see Authentication.
GET requests redirection
By default, when the server receive a request on a non-POST method, a “Method Not Allowed (405)” response is returned. Sometimes, you may need to allow GET requests to really return a page, for example to display some documentation about the RPC server.
For that use case, server can be configured with redirect_get_request_to argument. It allows any value accepted
by django.shortcuts.redirect function (see official redirect() docs). When configured, a permanent redirection
to the corresponding location will be returned on GET requests.
Namespace
Namespaces allow you to organize related RPC procedures under a common prefix. This is useful for:
Grouping related procedures together
Avoiding name conflicts between procedures
Providing a clearer API structure
Creating a namespace
To create a namespace, instantiate the RpcNamespace class:
Registering procedures
You can register procedures to a namespace using the register_procedure method, similar to how you
would with an RpcServer:
@math.register_procedure
def add(a, b):
return a + b
@math.register_procedure
def subtract(a, b):
return a - b
See Customize registration for a list of available customization options.
Registering a namespace to a server
To make the procedures in a namespace available through your RPC server, register the namespace to the server:
from modernrpc.server import RpcServer
from myapp.math import math
server = RpcServer()
server.register_namespace(math, "math")
This will make the procedures available under the prefix “math.”, so clients can call them as math.add and math.subtract.
If you don’t provide a name when registering a namespace, the procedures will be registered without a prefix:
Authentication
Namespaces can have their own authentication settings that override the server’s settings:
# Create a namespace with authentication
secure_math = RpcNamespace(auth=my_auth_callback)
@secure_math.register_procedure
def multiply(a, b):
return a * b
All procedures registered in the namespace will use the auth callback configured in the namespace.
Note
Configured authentication callback can be overridden at procedure level.
For more information about authentication, see Authentication.
Multiple servers definition
You can create multiple server.
from modernrpc.server import RpcServer
# Create multiple server instances
api_v1 = RpcServer()
api_v2 = RpcServer()
from django.urls import path
from myapp.rpc import api_v1, api_v2
urlpatterns = [
path('api/v1/', api_v1.view),
path('api/v2/', api_v2.view),
]
When multiple servers are defined, each server can register its own procedures. This is useful to have different functions performing the same task under the same name, but from a different path (a.k.a multiple API versions).
If needed, a procedure can be registered into multiple servers. This can be used to avoid code duplication when a specific procedure should run the same code for different paths.
Sync and async views
RpcServer exposes two HTTP entry points:
view: a regular (synchronous) Django view callable
async_view: an asynchronous Django view (a coroutine function)
They are feature-equivalent. Routing, serializers/deserializers selection, authentication/authorization, error handling, introspection methods and namespaces all behave exactly the same in both cases. The only difference is the execution model of the view itself.
When should you use async_view?
If your deployment stack is ASGI-based (e.g., Daphne, Uvicorn, Hypercorn) and your RPC procedures contain async code async_view lets Django run the request handler as a coroutine. Multiple in-flight RPC calls can then await I/O concurrently, which can improve throughput and tail latency under I/O-bound workloads.
If your procedures are all synchronous or you run under a purely WSGI stack, using view is perfectly fine; async_view will not provide benefits in that scenario.
Behavior details
Async procedures: Procedures declared with async def will run concurrently within the same event loop when invoked through async_view, allowing overlapping awaits on I/O operations. Each individual request still executes one procedure at a time, but the server can progress multiple requests concurrently.
Sync procedures: Synchronous procedures are still supported by async_view. Django will execute them in a threadpool (just like calling a sync view from an async context), so they work transparently, though without the concurrency advantages of native async code.
API parity: Configuration and registration are identical; there is nothing special to do when registering procedures or namespaces for async_view.
from django.urls import path
from modernrpc.server import RpcServer
rpc = RpcServer()
# register procedures, namespaces, auth, etc. on rpc as usual
urlpatterns = [
# Sync endpoint
path("/rpc", rpc.view),
# Async endpoint (same API, coroutine-based view)
path("/async_rpc", rpc.async_view),
]
Notes
Django has supported asynchronous views since 3.1; modernrpc supports Django 3.2 to 5.2. async_view will work on all supported Django versions when running under an ASGI server.
You can expose both endpoints simultaneously (as shown above). Clients can choose either; functionality is identical.