WebSockets
Websockets 是一种用于客户端和服务器之间进行双向、持久通信的协议。这与 HTTP 不同,HTTP 使用请求/响应模型,即客户端发送请求,服务器进行响应。而使用 websockets,任何一方都可以随时发送消息,另一方可以做出响应。
这使得构建不同类型的应用程序成为可能,包括聊天应用、实时更新的仪表盘和实时协作工具等。如果使用 HTTP,这些应用需要不断地轮询服务器以获取更新。
在 FastHTML 中,你可以使用 @app.ws 装饰器来创建一个 websocket 路由。这个装饰器接受一个路由路径,以及可选的 conn 和 disconn 参数,它们分别代表 websockets 中的 on_connect 和 on_disconnect 回调函数。被 @app.ws 装饰的函数是接收到消息时调用的主函数。
这是一个基本的 websocket 路由示例:
@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'))on_message 函数是接收到消息时调用的主函数,你可以随意命名。与标准路由类似,on_message 的参数会自动从 websocket 负载中为你解析,因此你无需手动解析消息内容。但是,某些参数名称被保留用于特殊目的。以下是最重要的几个:
send是一个函数,可用于向客户端发送文本数据。data是一个包含客户端发送数据的字典。ws是对 websocket 对象的引用。
例如,我们可以像这样向刚刚连接的客户端发送一条消息:
async def on_conn(send):
await send(Div('Hello, world!'))或者,如果我们从客户端收到一条消息,我们可以向他们回发一条消息:
@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')在客户端,我们可以使用 HTMX 的 websocket 扩展来打开一个 websocket 连接并发送/接收消息。例如:
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)这将在 /ws 路由上创建一个到服务器的 websocket 连接,并通过 websocket 将任何表单提交发送到服务器。然后,服务器会通过向客户端回发一条消息来响应。客户端将使用带外交换(Out of Band Swaps)技术,用来自服务器的消息更新消息 div,这意味着内容会被具有相同 id 的元素替换,而无需重新加载页面。
如果你想使用 websockets,请确保在创建 FastHTML 对象时设置 exts='ws',以便加载该扩展。
综上所述,客户端和服务器的代码应如下所示:
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()这是一个相当简单的例子,用标准的 HTTP 请求也能轻松实现,但它展示了 websockets 的基本工作原理。接下来,让我们看一个更复杂的例子。
Websocket 中的会话数据
会话数据在标准 HTTP 路由和 Websockets 之间是共享的。这意味着你可以在 websocket 处理程序中访问例如已登录用户的 ID。
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()实时聊天应用
让我们利用新学的 websocket 知识来构建一个简单的聊天应用。我们将创建一个应用,让多个用户可以实时发送和接收消息。
让我们从定义应用和主页开始:
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')现在,让我们来处理 websocket 连接。我们将为此添加一个新的路由,并附带一个 on_conn 和 on_disconn 函数来跟踪当前连接到 websocket 的用户。最后,我们将处理向所有已连接用户发送消息的逻辑。
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()我们现在可以用 python chat_ws.py 运行这个应用,并打开多个浏览器标签页访问 https://:5001。你应该能在一个标签页中发送消息,并在其他标签页中看到它们出现。
仍在开发中
这个页面(以及 FastHTML 中的 Websocket 支持)仍在开发中。欢迎提出问题、PR 和反馈!