Spaces:
Sleeping
Sleeping
| # WebSockets | |
| <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! --> | |
| Websockets are a protocol for two-way, persistent communication between | |
| a client and server. This is different from HTTP, which uses a | |
| request/response model where the client sends a request and the server | |
| responds. With websockets, either party can send messages at any time, | |
| and the other party can respond. | |
| This allows for different applications to be built, including things | |
| like chat apps, live-updating dashboards, and real-time collaborative | |
| tools, which would require constant polling of the server for updates | |
| with HTTP. | |
| In FastHTML, you can create a websocket route using the `@app.ws` | |
| decorator. This decorator takes a route path, and optional `conn` and | |
| `disconn` parameters representing the `on_connect` and `on_disconnect` | |
| callbacks in websockets, respectively. The function decorated by | |
| `@app.ws` is the main function that is called when a message is | |
| received. | |
| Here’s an example of a basic websocket route: | |
| ``` python | |
| @app.ws('/ws', conn=on_conn, disconn=on_disconn) | |
| async def on_message(msg:str, send): | |
| await send(Div('Hello ' + msg, id='notifications')) | |
| await send(Div('Goodbye ' + msg, id='notifications')) | |
| ``` | |
| The `on_message` function is the main function that is called when a | |
| message is received and can be named however you like. Similar to | |
| standard routes, the arguments to `on_message` are automatically parsed | |
| from the websocket payload for you, so you don’t need to manually parse | |
| the message content. However, certain argument names are reserved for | |
| special purposes. Here are the most important ones: | |
| - `send` is a function that can be used to send text data to the client. | |
| - `data` is a dictionary containing the data sent by the client. | |
| - `ws` is a reference to the websocket object. | |
| For example, we can send a message to the client that just connected | |
| like this: | |
| ``` python | |
| async def on_conn(send): | |
| await send(Div('Hello, world!')) | |
| ``` | |
| Or if we receive a message from the client, we can send a message back | |
| to them: | |
| ``` python | |
| @app.ws('/ws', conn=on_conn, disconn=on_disconn) | |
| async def on_message(msg:str, send): | |
| await send(Div('You said: ' + msg, id='notifications')) | |
| # or... | |
| return Div('You said: ' + msg, id='notifications') | |
| ``` | |
| On the client side, we can use HTMX’s websocket extension to open a | |
| websocket connection and send/receive messages. For example: | |
| ``` python | |
| from fasthtml.common import * | |
| app = FastHTML(exts='ws') | |
| @app.get('/') | |
| def home(): | |
| cts = Div( | |
| Div(id='notifications'), | |
| Form(Input(id='msg'), id='form', ws_send=True), | |
| hx_ext='ws', ws_connect='/ws') | |
| return Titled('Websocket Test', cts) | |
| ``` | |
| This will create a websocket connection to the server on route `/ws`, | |
| and send any form submissions to the server via the websocket. The | |
| server will then respond by sending a message back to the client. The | |
| client will then update the message div with the message from the server | |
| using Out of Band Swaps, which means that the content is swapped with | |
| the same id without reloading the page. | |
| <div> | |
| > **Note** | |
| > | |
| > Make sure you set `exts='ws'` when creating your | |
| > [`FastHTML`](https://www.fastht.ml/docs/api/core.html#fasthtml) object | |
| > if you want to use websockets so the extension is loaded. | |
| </div> | |
| Putting it all together, the code for the client and server should look | |
| like this: | |
| ``` python | |
| from fasthtml.common import * | |
| app = FastHTML(exts='ws') | |
| rt = app.route | |
| @rt('/') | |
| def get(): | |
| cts = Div( | |
| Div(id='notifications'), | |
| Form(Input(id='msg'), id='form', ws_send=True), | |
| hx_ext='ws', ws_connect='/ws') | |
| return Titled('Websocket Test', cts) | |
| @app.ws('/ws') | |
| async def ws(msg:str, send): | |
| await send(Div('Hello ' + msg, id='notifications')) | |
| serve() | |
| ``` | |
| This is a fairly simple example and could be done just as easily with | |
| standard HTTP requests, but it illustrates the basic idea of how | |
| websockets work. Let’s look at a more complex example next. | |
| ## Session data in Websockets | |
| Session data is shared between standard HTTP routes and Websockets. This | |
| means you can access, for example, logged in user ID inside websocket | |
| handler: | |
| ``` python | |
| from fasthtml.common import * | |
| app = FastHTML(exts='ws') | |
| rt = app.route | |
| @rt('/login') | |
| def get(session): | |
| session["person"] = "Bob" | |
| return "ok" | |
| @app.ws('/ws') | |
| async def ws(msg:str, send, session): | |
| await send(Div(f'Hello {session.get("person")}' + msg, id='notifications')) | |
| serve() | |
| ``` | |
| ## Real-Time Chat App | |
| Let’s put our new websocket knowledge to use by building a simple chat | |
| app. We will create a chat app where multiple users can send and receive | |
| messages in real time. | |
| Let’s start by defining the app and the home page: | |
| ``` python | |
| from fasthtml.common import * | |
| app = FastHTML(exts='ws') | |
| rt = app.route | |
| msgs = [] | |
| @rt('/') | |
| def home(): return Div( | |
| Div(Ul(*[Li(m) for m in msgs], id='msg-list')), | |
| Form(Input(id='msg'), id='form', ws_send=True), | |
| hx_ext='ws', ws_connect='/ws') | |
| ``` | |
| Now, let’s handle the websocket connection. We’ll add a new route for | |
| this along with an `on_conn` and `on_disconn` function to keep track of | |
| the users currently connected to the websocket. Finally, we will handle | |
| the logic for sending messages to all connected users. | |
| ``` python | |
| users = {} | |
| def on_conn(ws, send): users[str(id(ws))] = send | |
| def on_disconn(ws): users.pop(str(id(ws)), None) | |
| @app.ws('/ws', conn=on_conn, disconn=on_disconn) | |
| async def ws(msg:str): | |
| msgs.append(msg) | |
| # Use associated `send` function to send message to each user | |
| for u in users.values(): await u(Ul(*[Li(m) for m in msgs], id='msg-list')) | |
| serve() | |
| ``` | |
| We can now run this app with `python chat_ws.py` and open multiple | |
| browser tabs to `http://localhost:5001`. You should be able to send | |
| messages in one tab and see them appear in the other tabs. | |
| ### A Work in Progress | |
| This page (and Websocket support in FastHTML) is a work in progress. | |
| Questions, PRs, and feedback are welcome! | |