Skip to content

Commit 7429bde

Browse files
authored
start channels (django) support (#190)
* start channels (django) support * implemented django channels * version bump and dependencies
1 parent d181a3a commit 7429bde

21 files changed

+694
-3
lines changed

Diff for: CHANGELOG.rst

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Changelog
22
---------
33

4+
v0.4.19
5+
=======
6+
- Added Django Channels transport support for WebSocket communication
7+
48
v0.4.18
59
=======
610
- Fixed Stalette/FastAPI implementation and added example using FastAPI server

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ You may also install using some **extras**:
1818
| reactivex | [ReactiveX](https://reactivex.io/) ([v4](https://pypi.org/project/reactivex/)) integration | [Tutorial](https://rsocket.io/guides/rsocket-py/tutorial/reactivex) |
1919
| aiohttp | [aiohttp](https://docs.aiohttp.org/en/stable/) Websocket transport (server/client) | [Tutorial](https://rsocket.io/guides/rsocket-py/tutorial/websocket) |
2020
| fastapi | [fastapi](https://github.com/fastapi/fastapi) Websocket transport (server/client) | |
21+
| channels | Websocket transport (server only) using channels (django) | |
2122
| quart | [Quart](https://pgjones.gitlab.io/quart/) Websocket transport (server only) | |
2223
| quic | [QUIC](https://github.com/aiortc/aioquic) transport | |
2324
| websockets | [Websockets](https://github.com/python-websockets/websockets) transport (server only) | |

Diff for: docs/conf.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
2525

2626
# The full version, including alpha/beta/rc tags
27-
release = '0.4.18'
27+
release = '0.4.19'
2828

2929
# -- General configuration ---------------------------------------------------
3030

Diff for: examples/django_channels/README.md

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# RSocket with Django Channels
2+
3+
This example demonstrates how to use RSocket with Django Channels, allowing you to implement RSocket protocol support in your Django applications.
4+
5+
## Overview
6+
7+
Django Channels extends Django to handle WebSockets, and this integration allows you to use the RSocket protocol over those WebSocket connections. This enables:
8+
9+
- Reactive streaming capabilities in Django applications
10+
- Support for all RSocket interaction models (request-response, fire-and-forget, request-stream, request-channel)
11+
- Bidirectional communication between client and server
12+
13+
## Requirements
14+
15+
- Django 3.0+
16+
- Channels 4.0+
17+
- An ASGI server like Daphne or Uvicorn
18+
19+
## Installation
20+
21+
1. Install Django Channels:
22+
```bash
23+
pip install channels
24+
```
25+
26+
2. Install an ASGI server:
27+
```bash
28+
pip install daphne
29+
```
30+
31+
3. Configure your Django project to use Channels (see Django Channels documentation)
32+
33+
## Server Setup
34+
35+
1. Create a request handler for RSocket:
36+
```python
37+
from rsocket.payload import Payload
38+
from rsocket.request_handler import BaseRequestHandler
39+
40+
class Handler(BaseRequestHandler):
41+
async def request_response(self, payload: Payload):
42+
return Payload(b'Echo: ' + payload.data)
43+
```
44+
45+
2. Create an RSocket consumer using the factory:
46+
```python
47+
from rsocket.transports.channels_transport import rsocket_consumer_factory
48+
49+
RSocketConsumer = rsocket_consumer_factory(handler_factory=Handler)
50+
```
51+
52+
3. Add the consumer to your routing configuration:
53+
```python
54+
from django.urls import path
55+
from channels.routing import ProtocolTypeRouter, URLRouter
56+
57+
application = ProtocolTypeRouter({
58+
'websocket': URLRouter([
59+
path('rsocket', RSocketConsumer.as_asgi()),
60+
]),
61+
})
62+
```
63+
64+
## Client Usage
65+
66+
You can connect to your Django Channels RSocket server using any RSocket client. Here's an example using the Python client:
67+
68+
```python
69+
import asyncio
70+
import websockets
71+
from rsocket.helpers import single_transport_provider
72+
from rsocket.payload import Payload
73+
from rsocket.rsocket_client import RSocketClient
74+
from rsocket.transports.websockets_transport import WebsocketsTransport
75+
76+
async def main():
77+
async with websockets.connect('ws://localhost:8000/rsocket') as websocket:
78+
transport = WebsocketsTransport()
79+
handler_task = asyncio.create_task(transport.handler(websocket))
80+
81+
try:
82+
async with RSocketClient(single_transport_provider(transport)) as client:
83+
response = await client.request_response(Payload(b'Hello'))
84+
print(f"Received: {response.data.decode()}")
85+
finally:
86+
handler_task.cancel()
87+
try:
88+
await handler_task
89+
except asyncio.CancelledError:
90+
pass
91+
92+
if __name__ == '__main__':
93+
asyncio.run(main())
94+
```
95+
96+
## Advanced Usage
97+
98+
The Django Channels transport supports all RSocket interaction models:
99+
100+
- **Request-Response**: Simple request with a single response
101+
- **Fire-and-Forget**: One-way message with no response
102+
- **Request-Stream**: Request that receives a stream of responses
103+
- **Request-Channel**: Bi-directional stream of messages
104+
105+
See the server_example.py and client_example.py files for more detailed examples.
106+
107+
## Security Considerations
108+
109+
- Use secure WebSockets (wss://) in production
110+
- Implement proper authentication and authorization in your Django application
111+
- Consider using RSocket's authentication and authorization extensions for additional security

Diff for: examples/django_channels/__init__.py

Whitespace-only changes.

Diff for: examples/django_channels/client_example.py

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""
2+
Example of a client connecting to a Django Channels RSocket server.
3+
4+
This example shows how to create a client that connects to a Django Channels
5+
RSocket server and performs a request-response interaction.
6+
"""
7+
8+
import asyncio
9+
import logging
10+
import ssl
11+
from typing import Optional
12+
13+
import asyncclick as click
14+
import websockets
15+
16+
from rsocket.helpers import single_transport_provider
17+
from rsocket.payload import Payload
18+
from rsocket.rsocket_client import RSocketClient
19+
from rsocket.transports.websockets_transport import WebsocketsTransport
20+
21+
22+
async def connect_to_django_channels(url: str, ssl_context: Optional[ssl.SSLContext] = None):
23+
"""
24+
Connect to a Django Channels RSocket server using websockets.
25+
26+
:param url: WebSocket URL (e.g., 'ws://localhost:8000/rsocket')
27+
:param ssl_context: Optional SSL context for secure connections
28+
"""
29+
async with websockets.connect(url, ssl=ssl_context) as websocket:
30+
# Create a transport using the websocket connection
31+
transport = WebsocketsTransport()
32+
33+
# Start the transport handler
34+
handler_task = asyncio.create_task(transport.handler(websocket))
35+
36+
try:
37+
# Create an RSocket client using the transport
38+
async with RSocketClient(single_transport_provider(transport)) as client:
39+
# Send a request-response
40+
payload = Payload(b'Hello from RSocket client')
41+
response = await client.request_response(payload)
42+
43+
print(f"Received response: {response.data.decode()}")
44+
45+
# You can add more interactions here
46+
47+
finally:
48+
# Clean up the handler task
49+
handler_task.cancel()
50+
try:
51+
await handler_task
52+
except asyncio.CancelledError:
53+
pass
54+
55+
56+
@click.command()
57+
@click.option('--url', default='ws://localhost:8000/rsocket', help='WebSocket URL')
58+
@click.option('--secure', is_flag=True, help='Use secure WebSocket (wss://)')
59+
async def main(url: str, secure: bool):
60+
"""
61+
Connect to a Django Channels RSocket server.
62+
"""
63+
logging.basicConfig(level=logging.INFO)
64+
65+
if secure and not url.startswith('wss://'):
66+
url = 'wss://' + url.removeprefix('ws://')
67+
ssl_context = ssl.create_default_context()
68+
# Disable certificate verification for testing
69+
ssl_context.check_hostname = False
70+
ssl_context.verify_mode = ssl.CERT_NONE
71+
else:
72+
ssl_context = None
73+
74+
await connect_to_django_channels(url, ssl_context)
75+
76+
77+
if __name__ == '__main__':
78+
main()

Diff for: examples/django_channels/django_rsocket/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
db.sqlite3

Diff for: examples/django_channels/django_rsocket/__init__.py

Whitespace-only changes.

Diff for: examples/django_channels/django_rsocket/django_rsocket/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""
2+
ASGI config for django_rsocket project.
3+
4+
It exposes the ASGI callable as a module-level variable named ``application``.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
8+
"""
9+
10+
import os
11+
12+
from django.core.asgi import get_asgi_application
13+
14+
from channels.routing import ProtocolTypeRouter, URLRouter
15+
from .routing import websocket_urlpatterns
16+
17+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_rsocket.settings')
18+
19+
application = ProtocolTypeRouter({
20+
"http": get_asgi_application(),
21+
"websocket": URLRouter(websocket_urlpatterns),
22+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import logging
2+
3+
from channels.routing import ProtocolTypeRouter, URLRouter
4+
from django.urls import path
5+
6+
from rsocket.helpers import create_future
7+
from rsocket.payload import Payload
8+
from rsocket.request_handler import BaseRequestHandler
9+
from rsocket.transports.channels_transport import rsocket_consumer_factory
10+
11+
12+
# Define a request handler for RSocket
13+
class Handler(BaseRequestHandler):
14+
async def request_response(self, payload: Payload):
15+
logging.info(payload.data)
16+
17+
return create_future(Payload(b'Echo: ' + payload.data))
18+
19+
20+
# Create a consumer using the factory
21+
RSocketConsumer = rsocket_consumer_factory(handler_factory=Handler)
22+
23+
# Django Channels routing configuration
24+
application = ProtocolTypeRouter({
25+
'websocket': URLRouter([
26+
path('rsocket', RSocketConsumer.as_asgi()),
27+
]),
28+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.urls import path
2+
from .consumers import RSocketConsumer
3+
4+
websocket_urlpatterns = [
5+
path('rsocket', RSocketConsumer.as_asgi()),
6+
]

0 commit comments

Comments
 (0)