WebSocket Support in Panther#
WebSockets enable you to build interactive, real-time features such as chat, notifications, and live updates.
Structure & Requirements#
Creating a WebSocket Class#
Create a WebSocket handler class in app/websockets.py by inheriting from GenericWebsocket:
| app/websockets.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 | |
Registering WebSocket URLs#
Register your WebSocket class in app/urls.py:
| app/urls.py | |
|---|---|
1 2 3 4 5 | |
Panther supports WebSocket routing just like APIs.
How It Works#
- Client Connection: The client connects to your
ws/book/URL using the WebSocket protocol. - Connection Handling: The
connect()method of your WebSocket class is called. - Validation: You can validate the connection using
self.headers,self.query_params, etc. - Accept/Reject: Accept the connection with
self.accept(). If not accepted, it is rejected by default. - Connection ID: Each connection gets a unique
connection_id(accessible viaself.connection_id). You may want to store this in a database or cache. - Receiving Messages: Incoming messages are handled by the
receive()method. Messages can bestrorbytes. - Sending Messages:
- Within the WebSocket class: Use
self.send(data). - Outside the WebSocket class: Use
send_message_to_websocket():from panther.websocket import send_message_to_websocket await send_message_to_websocket(connection_id='7e82d57c9ec0478787b01916910a9f45', data='New Message From WS')
- Within the WebSocket class: Use
Advanced Usage#
Authentication#
You can enable authentication in your WebSocket class by setting auth to an async function or a class with an async __call__ method. Panther will use this callable to authenticate the user.
- If you do not set
auth, Panther will use the defaultWS_AUTHENTICATIONfrom your configuration only if the request contains an authorization header/ cookie/ param/ etc.. - If there is no authorization header, authentication is bypassed and
self.userwill beNone.
There are several built-in options, but we recommend QueryParamJWTAuthentication for WebSocket authentication.
WS_AUTHENTICATION = 'panther.authentications.QueryParamJWTAuthentication'
This will set self.user to a UserModel instance or None. The connection will be rejected if any exception occurs during authentication.
| app/websockets.py | |
|---|---|
1 2 3 4 5 6 7 8 9 | |
Note: When authentication is bypassed (no authorization header),
self.userwill beNoneand you must rely on permissions to check the user and their authorization.
Permissions#
You can implement your authorization logic using permission classes or functions. Any async function or class with an async __call__ method can be used as a permission. Panther will call each permission (asynchronously).
- If any return
False, the connection will be rejected.
Pass a list of permission callables to your WebSocket class.
- If you pass a single permission, it will be automatically wrapped in a list.
Each permission must be async (either an async function or a class with an async
__call__).
Example Permission Function:
| app/permissions.py | |
|---|---|
1 2 3 4 | |
Example Permission Class:
| app/permissions.py | |
|---|---|
1 2 3 4 5 6 | |
| app/websockets.py | |
|---|---|
1 2 3 4 5 6 7 8 | |
Multiple Workers & Redis#
- Recommended: For running WebSockets with multiple workers, add Redis to your configuration. See Adding Redis
- Without Redis: If you do not use Redis but want to run WebSockets with multiple workers (e.g., with Gunicorn), use the
--preloadflag:gunicorn -w 10 -k uvicorn.workers.UvicornWorker main:app --preload - Uvicorn Limitation: WebSockets do not work properly when using uvicorn directly with the
--workersflag (e.g.,uvicorn main:app --workers 4). This is because each worker process maintains its own separate WebSocket connections, and there's no shared state between workers. Use Gunicorn with the--preloadflag or add Redis for proper WebSocket support with multiple workers.
Closing Connections#
- Within the WebSocket class:
from panther import status await self.close(code=status.WS_1000_NORMAL_CLOSURE, reason='Closing connection') - Outside the WebSocket class:
from panther import status from panther.websocket import close_websocket_connection await close_websocket_connection(connection_id='7e82d57c9ec0478787b01916910a9f45', code=status.WS_1008_POLICY_VIOLATION, reason='Closing connection')
Path Variables#
You can define path variables in your WebSocket URL. These will be passed to the connect() method:
1 2 3 4 5 6 7 8 9 | |
Example#
Example Client Code#
Here's a simple example using JavaScript:
| websocket.js | |
|---|---|
1 2 3 4 5 6 7 8 9 10 | |
Echo Example#
Full echo example with WebSocket:
| main.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
panther run main:app and visit http://127.0.0.1:8000.
Tips & Notes#
- Connection Validation: Always validate connections in
connect()using headers or query parameters as needed. - Connection IDs: Store
connection_idif you need to send messages to clients outside the WebSocket class. - Multiple Workers: Use Redis for scaling WebSockets across multiple workers.
- Error Handling: Implement error handling in your WebSocket methods for production use.
- Security: Always validate and sanitize incoming data.
Enjoy building with Panther WebSockets!