FastAPI: websocket的用法及举例

1. Websocket

1.1 Websocket介绍

WebSocket 是一种在单个TCP连接上进行全双工通信的协议,允许客户端和服务器之间相互发送数据,而不需要像传统的HTTP请求-响应模型那样频繁建立和断开连接。
全双工通信(Full-Duplex Communication)是一种通信模式,允许通信双方同时发送和接收数据。换句话说,数据可以同时从两端双向传输,而不会相互阻塞或干扰。

1.2 FastAPI中的Websocket

FastAPI提供了对WebSocket的原生支持,允许你轻松构建高效的实时应用,如聊天室、实时数据更新等。

1.2.1 装饰器

FastAPI中与WebSocket相关的主要装饰器为 @app.websocket。该装饰器的作用和参数如下:

  • 作用 :将一个路径(如/ws)与一个处理WebSocket请求的函数关联。当客户端通过WebSocket连接该路径时,FastAPI会调用该函数处理连接和通信。
  • 参数:它接受的参数与其他路由装饰器相同,主要是路径(URL),可选地也能设置依赖项、权限等。

代码举例如下(当客户端通过WebSocket连接/ws路径时,FastAPI将执行下面的websocket_endpoint函数):

python 复制代码
from fastapi import FastAPI, WebSocket
app = FastAPI()
# 定义一个 WebSocket 路由
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()  # 接受 WebSocket 连接
    while True:
        data = await websocket.receive_text()  # 接收来自客户端的消息
        await websocket.send_text(f"Message text was: {data}")  # 回复消息给客户端
1.2.2 websocket相关方法

FastAPI提供了处理WebSocket各种事件的方法,包括接受消息、发送消息、关闭连接等。具体如下:

  • websocket.accept:接受WebSocket连接请求。
  • websocket.receive_text:接收客户端发来的文本消息。
  • websocket.send_text:向客户端发送文本消息。
  • websocket.close:关闭WebSocket连接。

2. 构建对话机器人

这里我们用FastAPI和React构建一个聊天机器人的聊天界面。这里关于机器人的后端处理逻辑这里不做详细介绍,而前端部分主要介绍App.tsx和ChatPag.tsx内容,不介绍CSS部分。具体代码如下:

React中App.tsx代码如下:

typescript 复制代码
import './App.css';
import ChatPage from './components/ChatPage';

function App() {
  return (
    <div className="App">
        <div className="header">
          <div className="header-logo">
            <img src="https://cdn.builder.io/api/v1/image/assets/TEMP/b0db057162d379f22892cd5ae4d13c509717e0a81da39be3f65cb94e15556ed7?apiKey=0682bce60b3549f085131079f1bf89f0&&apiKey=0682bce60b3549f085131079f1bf89f0" alt="Chainlit" /> 
            <div className="header-title">SmartRecommend服务推荐助手</div>
          </div>
        </div>
        <div className='body-container'>
          <div className="main">
            <div className='chatpage'>
              <ChatPage />
            </div>
          </div>
        </div>
      </div>
  );
}
export default App;

React中ChatPage.tsx代码如下:

typescript 复制代码
import "./ChatPage.css";
import { useEffect, useState} from "react";
import { nanoid } from 'nanoid';

interface Message{
    id:string,
    name:string,
    type:string,
    output:string,
    createdAt:number|string,
}
function ChatPage() {
    const [inputValue, setInputValue] = useState("");
    const [messages,setMessages] = useState<Message[]>([]);
    const [socket,setSocket] = useState<WebSocket|null>(null);
    
    useEffect(() => {
        const ws = new WebSocket("ws://localhost:8000/ws/chat");
        ws.onopen = () => {
          console.log("websocket链接已建立!");
        };
        ws.onmessage = (event) => {
          const message = JSON.parse(event.data);
          setMessages((prevMessages) => [...prevMessages, message]);
        };
        ws.onerror = (error) => {
            console.log('WebSocket错误:', error);
        };
        ws.onclose=()=>{
            console.log("websocket链接已关闭!");
        }
        setSocket(ws);
        return () => {
            ws.close();
        }
    },[]);

    const handleSendMessage = () => {
        const content = inputValue.trim();
        if (content) {
          const message: Message={
            id: nanoid(),
            name: "User",
            type: "user_message",
            output: content,
            createdAt: Date.now(),
          };
        setMessages((prevMessages) => [...prevMessages, message]);
        socket?.send(JSON.stringify(message));
        }
        setInputValue("");
      };

    const renderMessage = (message:Message,index:number) => {
        const dateOptions: Intl.DateTimeFormatOptions = {
            hour: "2-digit",
            minute: "2-digit",
          };
          const date = new Date(message.createdAt).toLocaleTimeString(
            undefined,
            dateOptions
          );
          if(message.type === "user_message") {
            return (
                <div key={message.id} className="chat-box-user">
                  <div className="user-avatar">U</div>
                  <div className="bot-user-content">
                    <div className="user-icon">
                      <div className="bot-user-name">{message.name}</div>
                      <div className="bot-user-time">{date}</div>
                    </div>
                    <div className="user-chat-message">{message.output}</div>
                  </div>
                </div>
            );
          } else {
              return (
                <div key={message.id} className="chat-box-bot">
                  <div className="bot-avatar">B</div>
                  <div className="bot-user-content">
                    <div className="bot-icon">
                        <div className="bot-user-name">{message.name}</div>
                        <div className="bot-user-time">{date}</div>
                    </div>
                    <div className="bot-chat-message">{message.output}</div>
                  </div>
                </div>
              );
          };
        };
    return (
        <div className="chat-container">
        <div className="chat-box">
            {messages.map(renderMessage)}
        </div>
        <div className="fixed-bottom">
          <input className="fixed-bottom-input" 
                type="text"
                value={inputValue}
                placeholder="你可以输入"你好"唤醒服务"
                onChange={(e) => setInputValue(e.target.value)}
                onKeyUp={(e) => {
                  if (e.key === "Enter") {
                    handleSendMessage();
                  }
                }}>
          </input>
          <button onClick={handleSendMessage} className="button" type="submit">Send</button>
        </div>
      </div>
    ); 
}
export default ChatPage;

后端FastAPI代码:

python 复制代码
from fastapi import FastAPI, WebSocket,HTTPException
import uvicorn
from fastapi.middleware.cors import CORSMiddleware
from typing import List
import json
import datetime
from nanoid import generate
import httpx

app=FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
clients: List[WebSocket] = []

RASA_API_URL="http://localhost:5005/webhooks/rest/webhook"

@app.websocket("/ws/chat")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    clients.append(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            for client in clients:
                text={"id": generate(),
                      "name":"Bot","type":"bot_message",
                      "output":json.loads(data)["output"],
                      "createdAt":int(datetime.datetime.now().timestamp()*1000)}
                text=json.dumps(text)
                await client.send_text(text)
    except Exception as e:
        print(e)
        clients.remove(websocket)
        
if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
相关推荐
eszcx203943 小时前
ip经过多个服务器转发会网速变慢吗
服务器·网络协议·tcp/ip
EasyCVR5 小时前
安防监控/视频系统EasyCVR视频汇聚平台如何过滤134段的告警通道?
网络协议·音视频·视频编解码·视频监控·gb28181
engchina8 小时前
WSL2 中配置桥接模式、虚拟交换机及固定 IP
网络协议·tcp/ip·桥接模式
Bonne journée10 小时前
socket和http区别
网络·网络协议·http
貂蝉空大12 小时前
uni-app 封装websocket 心跳检测,开箱即用
websocket·网络协议·uni-app
hgdlip13 小时前
手机改IP地址怎么弄?全面解析与操作指南
网络协议·tcp/ip·智能手机
KookeeyLena513 小时前
每一个云手机的ip是独立的吗
网络协议·tcp/ip·智能手机
无敌开心15 小时前
fastAPI教程:进阶操作
计算机视觉·fastapi
狼爷17 小时前
深度解析:从浏览器输入链接到页面展现的奇幻历程
后端·网络协议