Trio WebSocket
Trio WebSocket is a Python library that provides a WebSocket implementation for the Trio asynchronous I/O framework. It offers both client and server functionalities, built on the `wsproto` sans-IO state machine, emphasizing safety, correctness, and ergonomics. The current stable version is 0.12.2. While mature and stable, the project is currently in 'life-support maintenance', with contributions welcomed.
Warnings
- gotcha The `trio-websocket` project is currently in 'life-support maintenance'. While stable, active development and new features may be slow, and future major changes might occur with limited dedicated support.
- gotcha When sending data, be mindful of the type (string or bytes). If a server is configured with `encoding="text"` but receives raw bytes, it might process them unexpectedly (e.g., as `None` or an empty string), even though `send_message` theoretically accepts both.
- gotcha Unlike some other async libraries where `BrokenStreamError` might be raised on a closed connection, `trio-websocket` raises `ConnectionClosed` when attempting to write to a connection that has already been closed.
- gotcha Trio generally encourages caller-enforced timeouts using cancel scopes (`trio.fail_after`). However, `trio-websocket`'s high-level APIs (like `open_websocket_url` and server setup) incorporate internal timeouts for safety and ergonomics. This can be a point of confusion for users accustomed to Trio's 'timeout for humans' philosophy.
Install
-
pip install trio-websocket
Imports
- open_websocket_url
from trio_websocket import open_websocket_url
- open_websocket
from trio_websocket import open_websocket
- serve_websocket
from trio_websocket import serve_websocket
- ConnectionClosed
from trio_websocket import ConnectionClosed
- WebSocketRequest
from trio_websocket import WebSocketRequest
- WebSocketConnection
from trio_websocket import WebSocketConnection
Quickstart
import trio
import logging
from trio_websocket import serve_websocket, open_websocket_url, ConnectionClosed, WebSocketRequest
logging.basicConfig(level=logging.INFO)
async def echo_server_handler(request: WebSocketRequest) -> None:
"""Reverses incoming websocket messages and sends them back."""
logging.info('Server: Handler starting on path "%s"', request.path)
ws = await request.accept()
try:
while True:
message = await ws.get_message()
logging.info('Server: Received message: %s', message)
await ws.send_message(message[::-1]) # Echo reversed message
except ConnectionClosed:
logging.info('Server: Connection closed.')
except Exception as e:
logging.error('Server: Error in handler: %s', e)
finally:
logging.info('Server: Handler exiting.')
async def client_task(port: int) -> None:
"""Connects to the server, sends a message, and waits for a response."""
uri = f'ws://127.0.0.1:{port}/echo'
logging.info('Client: Connecting to %s', uri)
try:
async with open_websocket_url(uri) as ws:
test_message = 'hello trio-websocket'
logging.info('Client: Sending: "%s"', test_message)
await ws.send_message(test_message)
received_message = await ws.get_message()
expected_message = test_message[::-1] # Reversed
logging.info('Client: Received: "%s"', received_message)
assert received_message == expected_message
logging.info('Client: Message echoed and reversed successfully!')
except OSError as ose:
logging.error('Client: Connection attempt failed: %s', ose)
except Exception as e:
logging.error('Client: Error in client task: %s', e)
finally:
logging.info('Client: Task finished.')
async def main():
server_port = 8000
async with trio.open_nursery() as nursery:
logging.info(f'Main: Spawning server on 127.0.0.1:{server_port}')
nursery.start_soon(serve_websocket, echo_server_handler, '127.0.0.1', server_port, None)
# Give the server a moment to start up
await trio.sleep(0.1)
logging.info('Main: Spawning client task.')
nursery.start_soon(client_task, server_port)
if __name__ == '__main__':
try:
trio.run(main)
except KeyboardInterrupt:
logging.info('Program interrupted by user.')