原文链接: Simple chat application using Websockets with FastAPI
作者: Gealber Morales Gealber Morales
嘿,伙计们,我又在摆弄 FastAPI 了。这次我将尝试构建一个非常简单的聊天 Web 应用程序。我将使用 Websockets 和 FastAPI。
完整解释 Websockets 需要一篇文章,所以我假设读者对这个协议有一定的了解。这更像是 HTTP 的升级,而不是协议本身。
另一个有效的观点是,这是我修改 FastAPI 文档的结果。因此,我没有深入地教授更好的方法。在另一篇文章中,我将向您介绍一种更成熟的方法来解决这个问题。现在让我们享受学习的乐趣。
我们想要建立什么?对要求的部分描述
在我们开始编码之前,让我们先想想我们想要做什么。这个想法是建立一个简单的网页,模拟聊天室的行为。
期望的操作:
- 在浏览器上打开页面。
- 所有连接到同一 URL 的客户端,但通过不同的浏览器,可以在它们之间聊天。
行为的基本设计
客户端 1 和客户端 2 连接到URL,假设是 http://localhost:8000
。在此连接之后,来自客户端1的每个消息将被广播到房间上连接的其他客户端,在这种情况下将是客户端 2。因此,该模式的广义视图将如 ASCII 图片中所示:
lua
Client 1
___T_ SERVER (FastAPI)
| O O | +---------+
|__n__| Message 1 | |
>===]__o[===< ---------------> | |
[o__] Message 2 | |
]| |[ <-------------- | |
[_| |_] | |
| |
| |
Client 2 | |
| |
___T_ | |
| O O | | |
|__n__| Message 2 | |
>===]__o[===< ---------------> | |
[o__] Message 1 | |
]| |[ <-------------- | |
[_| |_] +---------+
使用 go-asciibot 生成的机器人脚本
客户端 1 向服务器发送消息,服务器将消息广播给房间中的其他客户端。
依赖库
- FastAPI
ruby
$ pip install fastapi
- Uvicorn
ruby
$ pip install uvicorn
服务器代码
让我们进入代码。我将它分为两部分,一部分用于服务器代码,另一部分用于客户端代码。
正如我之前所说,我需要一个端点来服务 HTML 页面,在本例中,它将包含客户端代码。因此,我们需要学习如何使用 FastAPI 提供 HTML 。我们的 HTML 代码将在文件 index.html
上,与 main.py
在同一个文件夹中。
python
from fastapi import FastAPI
app = FastAPI()
html = ""
with open('index.html', 'r') as f:
html = f.read()
@app.get("/")
async def get():
return HTMLResponse(html)
这是怎么回事首先,我们简单导入 FastAPI 类,以便能够创建我们的应用程序。接下来,我们读取 index.html
文件的内容,并将其内容存储在名为 html 的变量中。一定有更好的办法,但对我们来说,这解决了我们的问题。
你可能想知道 HTML 文件的内容是什么,我们会看到不要担心。
最后,我们定义我们的端点,在那里我们可以访问 index.html
文件的内容。这样,如果您访问http://localhost:8000
,浏览器将呈现 HTML。
接下来怎么办?到目前为止,我们只能呈现 HTML 的内容,但我们还没有处理其他情况,对吗?如何区分一个客户和另一个客户?让我们解决这个问题。
python
from typing import List
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse
class ConnectionManager:
def __init__(self):
self.connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.connections.append(websocket)
async def broadcast(self, data: str):
for connection in self.connections:
await connection.send_text(data)
manager = ConnectionManager()
@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
await manager.connect(websocket)
while True:
data = await websocket.receive_text()
await manager.broadcast(f"Client {client_id}: {data}")
名为 ConnectionManager
的类,顾名思义,是我们将要用来处理不同客户端连接的类。
- 该类的
connect
方法,将 WebSocket 客户端作为参数。此客户端开始接受来自浏览器的消息,并添加到所有客户端的列表中。 broadcast
方法,将message
作为参数,并将其内容广播给房间中的任何其他客户端。
每个客户端都将连接到 http://localhost:8000/ws/{client_id}
,其中 {client_id}
将是标识此客户端的整数。连接后,它将开始接收来自浏览器的消息。
此消息中的任何一条都将在稍后广播给房间中的任何客户端。
客户端代码
现在来看看客户端代码,在我们的例子中将是 HTML 代码。
html
<!DOCTYPE html>
<html>
<head>
<title>Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<button onClick="showForm(event)" id="connect">Connect</button>
<form action="" onsubmit="sendMessage(event)" id="form" style="display: none">
<input type="text" id="messageText" autocomplete="off"/>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
var clientID = Date.now();
var ws = new WebSocket(`ws://localhost:8000/ws/${clientID}`);
function processMessage(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content);
messages.appendChild(message);
}
ws.onmessage = processMessage;
function sendMessage(event) {
var input = document.getElementById("messageText")
var message = document.createElement('li')
var content = document.createTextNode(input.value)
message.appendChild(content);
messages.appendChild(message);
ws.send(input.value);
input.value = ''
event.preventDefault()
}
function showForm(event) {
var button = document.getElementById("connect");
var form = document.getElementById("form");
button.style.display = "none";
form.style.display = "block";
}
</script>
</body>
</html>
Ups!!这是一个很大的信息,所以让我们把它分块,并试图理解至少最重要的部分。纯 HTML 有两个主要组成部分,表单和按钮。
- 按钮。单击后,按钮将对客户端隐藏,表单将可见。别再耍花招了。这是通过
showForm
方法上的javascript
完成的。 - 该表单包含一个用于写入消息的输入字段,以及一个将触发 JavaScript 端的方法
sendMessage
的send
按钮。
按下 send
按钮后,我们将使用 WebSocket 连接将文本发送到服务器。通过这种方式,当服务器收到消息时,它会将其广播给房间中的任何其他客户端。
它怎么跑的?
shell
uvicorn main:app --reload
--reload
标志是为了能够在失败时恢复,非常有用。
测试应用程序
打开至少两个 Web 浏览器并连接到 localhost:8000