Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Changelog
---------

v0.4.19
=======
- Added Django Channels transport support for WebSocket communication

v0.4.18
=======
- Fixed Stalette/FastAPI implementation and added example using FastAPI server
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ You may also install using some **extras**:
| reactivex | [ReactiveX](https://reactivex.io/) ([v4](https://pypi.org/project/reactivex/)) integration | [Tutorial](https://rsocket.io/guides/rsocket-py/tutorial/reactivex) |
| aiohttp | [aiohttp](https://docs.aiohttp.org/en/stable/) Websocket transport (server/client) | [Tutorial](https://rsocket.io/guides/rsocket-py/tutorial/websocket) |
| fastapi | [fastapi](https://github.com/fastapi/fastapi) Websocket transport (server/client) | |
| channels | Websocket transport (server only) using channels (django) | |
| quart | [Quart](https://pgjones.gitlab.io/quart/) Websocket transport (server only) | |
| quic | [QUIC](https://github.com/aiortc/aioquic) transport | |
| websockets | [Websockets](https://github.com/python-websockets/websockets) transport (server only) | |
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
author = '[email protected]'

# The full version, including alpha/beta/rc tags
release = '0.4.18'
release = '0.4.19'

# -- General configuration ---------------------------------------------------

Expand Down
111 changes: 111 additions & 0 deletions examples/django_channels/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# RSocket with Django Channels

This example demonstrates how to use RSocket with Django Channels, allowing you to implement RSocket protocol support in your Django applications.

## Overview

Django Channels extends Django to handle WebSockets, and this integration allows you to use the RSocket protocol over those WebSocket connections. This enables:

- Reactive streaming capabilities in Django applications
- Support for all RSocket interaction models (request-response, fire-and-forget, request-stream, request-channel)
- Bidirectional communication between client and server

## Requirements

- Django 3.0+
- Channels 4.0+
- An ASGI server like Daphne or Uvicorn

## Installation

1. Install Django Channels:
```bash
pip install channels
```

2. Install an ASGI server:
```bash
pip install daphne
```

3. Configure your Django project to use Channels (see Django Channels documentation)

## Server Setup

1. Create a request handler for RSocket:
```python
from rsocket.payload import Payload
from rsocket.request_handler import BaseRequestHandler

class Handler(BaseRequestHandler):
async def request_response(self, payload: Payload):
return Payload(b'Echo: ' + payload.data)
```

2. Create an RSocket consumer using the factory:
```python
from rsocket.transports.channels_transport import rsocket_consumer_factory

RSocketConsumer = rsocket_consumer_factory(handler_factory=Handler)
```

3. Add the consumer to your routing configuration:
```python
from django.urls import path
from channels.routing import ProtocolTypeRouter, URLRouter

application = ProtocolTypeRouter({
'websocket': URLRouter([
path('rsocket', RSocketConsumer.as_asgi()),
]),
})
```

## Client Usage

You can connect to your Django Channels RSocket server using any RSocket client. Here's an example using the Python client:

```python
import asyncio
import websockets
from rsocket.helpers import single_transport_provider
from rsocket.payload import Payload
from rsocket.rsocket_client import RSocketClient
from rsocket.transports.websockets_transport import WebsocketsTransport

async def main():
async with websockets.connect('ws://localhost:8000/rsocket') as websocket:
transport = WebsocketsTransport()
handler_task = asyncio.create_task(transport.handler(websocket))

try:
async with RSocketClient(single_transport_provider(transport)) as client:
response = await client.request_response(Payload(b'Hello'))
print(f"Received: {response.data.decode()}")
finally:
handler_task.cancel()
try:
await handler_task
except asyncio.CancelledError:
pass

if __name__ == '__main__':
asyncio.run(main())
```

## Advanced Usage

The Django Channels transport supports all RSocket interaction models:

- **Request-Response**: Simple request with a single response
- **Fire-and-Forget**: One-way message with no response
- **Request-Stream**: Request that receives a stream of responses
- **Request-Channel**: Bi-directional stream of messages

See the server_example.py and client_example.py files for more detailed examples.

## Security Considerations

- Use secure WebSockets (wss://) in production
- Implement proper authentication and authorization in your Django application
- Consider using RSocket's authentication and authorization extensions for additional security
Empty file.
78 changes: 78 additions & 0 deletions examples/django_channels/client_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
Example of a client connecting to a Django Channels RSocket server.

This example shows how to create a client that connects to a Django Channels
RSocket server and performs a request-response interaction.
"""

import asyncio
import logging
import ssl
from typing import Optional

import asyncclick as click
import websockets

from rsocket.helpers import single_transport_provider
from rsocket.payload import Payload
from rsocket.rsocket_client import RSocketClient
from rsocket.transports.websockets_transport import WebsocketsTransport


async def connect_to_django_channels(url: str, ssl_context: Optional[ssl.SSLContext] = None):
"""
Connect to a Django Channels RSocket server using websockets.

:param url: WebSocket URL (e.g., 'ws://localhost:8000/rsocket')
:param ssl_context: Optional SSL context for secure connections
"""
async with websockets.connect(url, ssl=ssl_context) as websocket:
# Create a transport using the websocket connection
transport = WebsocketsTransport()

# Start the transport handler
handler_task = asyncio.create_task(transport.handler(websocket))

try:
# Create an RSocket client using the transport
async with RSocketClient(single_transport_provider(transport)) as client:
# Send a request-response
payload = Payload(b'Hello from RSocket client')
response = await client.request_response(payload)

print(f"Received response: {response.data.decode()}")

# You can add more interactions here

finally:
# Clean up the handler task
handler_task.cancel()
try:
await handler_task
except asyncio.CancelledError:
pass


@click.command()
@click.option('--url', default='ws://localhost:8000/rsocket', help='WebSocket URL')
@click.option('--secure', is_flag=True, help='Use secure WebSocket (wss://)')
async def main(url: str, secure: bool):
"""
Connect to a Django Channels RSocket server.
"""
logging.basicConfig(level=logging.INFO)

if secure and not url.startswith('wss://'):
url = 'wss://' + url.removeprefix('ws://')
ssl_context = ssl.create_default_context()
# Disable certificate verification for testing
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
else:
ssl_context = None

await connect_to_django_channels(url, ssl_context)


if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions examples/django_channels/django_rsocket/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
db.sqlite3
Empty file.
Empty file.
22 changes: 22 additions & 0 deletions examples/django_channels/django_rsocket/django_rsocket/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
ASGI config for django_rsocket project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

from channels.routing import ProtocolTypeRouter, URLRouter
from .routing import websocket_urlpatterns

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_rsocket.settings')

application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": URLRouter(websocket_urlpatterns),
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import logging

from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path

from rsocket.helpers import create_future
from rsocket.payload import Payload
from rsocket.request_handler import BaseRequestHandler
from rsocket.transports.channels_transport import rsocket_consumer_factory


# Define a request handler for RSocket
class Handler(BaseRequestHandler):
async def request_response(self, payload: Payload):
logging.info(payload.data)

return create_future(Payload(b'Echo: ' + payload.data))


# Create a consumer using the factory
RSocketConsumer = rsocket_consumer_factory(handler_factory=Handler)

# Django Channels routing configuration
application = ProtocolTypeRouter({
'websocket': URLRouter([
path('rsocket', RSocketConsumer.as_asgi()),
]),
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.urls import path
from .consumers import RSocketConsumer

websocket_urlpatterns = [
path('rsocket', RSocketConsumer.as_asgi()),
]
Loading
Loading