Support RSocket
Skip to main content

Request routing

In the previous step we added a single request-response handler. In order to allow more than one functionality to use this handler, (e.g. login, messages, join/leave chanel) they need to be distinguished from each other. To achieve this, each request to the server will be identified by a route. This is similar to paths in an HTTP URL where each URL may handle one of the HTTP methods (eg. GET, POST). To implement this we will use the RequestRouter and RoutingRequestHandler classes.

See resulting code on GitHub

Server side

We will modify the example from the previous step into a routed request response.

Routing request handler

The handler_factory method below replaces the Handler class from the previous step:

from typing import Awaitable

from rsocket.frame_helpers import ensure_bytes
from rsocket.helpers import utf8_decode, create_response
from rsocket.payload import Payload
from rsocket.routing.request_router import RequestRouter
from rsocket.routing.routing_request_handler import RoutingRequestHandler

def handler_factory() -> RoutingRequestHandler:
router = RequestRouter()

@router.response('login')
async def login(payload: Payload) -> Awaitable[Payload]:
username = utf8_decode(payload.data)
return create_response(ensure_bytes(f'Welcome to chat, {username}'))

return RoutingRequestHandler(router)

Line 10 instantiates the RequestRouter. The methods on this helper are used as decorators to register the route of each request (it is similar to Flask and Quart syntax).

The RequestRouter has a decorator method for each RSocket request type:

  • response
  • stream
  • channel
  • fire_and_forget
  • metadata_push

All of the above methods receive a single argument, a string representing the route. The decorators may be applied multiple times which allows for aliases for the same route.

Lines 12-15 define the login method and attach it to a request-response with the login route. The method name does not have to match the route.

All methods decorated by the request router accept two optional arguments:

  • Any argument named composite_metadata (regardless of type-hint), or type-hinted with CompositeMetadata will receive a CompositeMetadata instance containing parsed composite metadata
  • Any other argument without a type-hint, or type-hinted with Payload will receive the request's payload

The RequestRouter has additional functionality which will be covered in later sections.

Line 17 returns the actual request handler, an instance of RoutingRequestHandler, which uses the request router instance.

Use the routing request handler

Modify the RSocketServer instantiation from the previous example and pass the handler_factory method as the handler_factory parameter:

from rsocket.rsocket_server import RSocketServer
from rsocket.transports.tcp import TransportTCP

async def run_server():
def session(*connection):
RSocketServer(TransportTCP(*connection), handler_factory=handler_factory)

Client side

Let's modify the client side to call this new routed request. For readability and maintainability, we will create a ChatClient which will wrap the RSocket client and provide the methods for interacting with the server.

ChatClient class

Below is the complete code for the new client.py module:

from rsocket.extensions.helpers import composite, route
from rsocket.frame_helpers import ensure_bytes
from rsocket.helpers import utf8_decode
from rsocket.payload import Payload
from rsocket.rsocket_client import RSocketClient

class ChatClient:
def __init__(self, rsocket: RSocketClient):
self._rsocket = rsocket

async def login(self, username: str):
payload = Payload(ensure_bytes(username), composite(route('login')))
response = await self._rsocket.request_response(payload)
print(f'Server response: {utf8_decode(response.data)}')

Lines 7-14 define our new ChatClient which will encapsulate the methods used to interact with the chat server.

Lines 11-14 define a login method. It uses the composite and route helper methods to create the metadata which will ensure the payload is routed to the method registered on the server side in the previous step.

Test the new functionality

Let's modify the client module to test our new ChatClient:

from rsocket.extensions.mimetypes import WellKnownMimeTypes
from rsocket.helpers import single_transport_provider
from rsocket.rsocket_client import RSocketClient
from rsocket.transports.tcp import TransportTCP

async def main():
...
async with RSocketClient(single_transport_provider(TransportTCP(*connection)),
metadata_encoding=WellKnownMimeTypes.MESSAGE_RSOCKET_COMPOSITE_METADATA) as client:
user = ChatClient(client)
await user.login('George')

Line 9 changes the metadata_encoding type to be COMPOSITE_METADATA. This is required for routing support.

Lines 10-11 instantiate a ChatClient and call the login method.