Python的Fastapi框架使用Websocket时Unsupported upgrade request问题以及修改Fastapi框架Websocket官方示例实现多房间多人即时通迅应用实例

一、Python的Fastapi框架使用Websocket时Unsupported upgrade request

    Python的Fastapi框架中可以方便地使用Websocket,见官方文档:https://fastapi.tiangolo.com/advanced/websockets/ 但在使用websocket的时候碰到了报错,WARNING:  Unsupported upgrade request.即当前的fastapi环境中并没有支持websocket的upgrade请求,客户端中的连接ws协议的js代码中也报错:handshake: Unexpected response code: 400。

    我的运行环境是uvicorn,是通过pip install uvicorn来安装,在之前开发API接口的时候一切正常,但在使用websockets时遇到了这个错误。是什么原因呢?原来通过pip install uvicorn来安装的uvicorn 0.12.2版本并没有带有websockets,要安装带有websockets功能的uvicorn需要使用pip install uvicorn[standard]来安装。解决方法先卸载再安装:

$ pip uninstall uvicorn
Found existing installation: uvicorn 0.12.2
Uninstalling uvicorn-0.12.2:
Proceed (y/n)? y
 Successfully uninstalled uvicorn-0.12.2
$ pip install uvicorn[standard]
Collecting uvicorn[standard]
  Downloading uvicorn-0.13.3-py3-none-any.whl (45 kB)
Collecting websockets==8.*; extra == "standard"
  Downloading websockets-8.1-cp38-cp38-win_amd64.whl (66 kB)
Installing collected packages: watchgod, websockets, uvicorn
Successfully installed uvicorn-0.13.3 watchgod-0.6 websockets-8.1

    从上面可以看到在使用uvicorn[standard]时有明确的提示,我们同时安装了websockets-8.1。这是uvicorn[standard]与uvicorn的主要区别,但不仅仅只有这个区别,找到官网的相关介绍:uvicorn · PyPI

$ pip install uvicorn[standard]
This will install uvicorn with minimal (pure Python) dependencies.
This will install uvicorn with "Cython-based" dependencies (where possible) and other "optional extras".
In this context, "Cython-based" means the following:
	the event loop uvloop will be installed and used if possible.
	the http protocol will be handled by httptools if possible.
	Moreover, "optional extras" means that:

the websocket protocol will be handled by websockets (should you want to use wsproto you'd need to install it manually) if possible.
the --reloader flag in development mode will use watchgod.
windows users will have colorama installed for the colored logs.
python-dotenv will be installed should you want to use the --env-file option.
PyYAML will be installed to allow you to provide a .yaml file to --log-config, if desired.

    从上面可以看到除了安装websockets外,在uvloop,http协议方面也有变化,还会自动增加对env文件的支持, 除此之外,还会对--reloader有影响,之前在进行开发时使用unicorn main:app启动项目添加 --reloader 选项从而实现项目热更新,而更换成了uvicorn[standard]之后,添加--reloader 选项启动项目会遇到项目一直报警:WARNING WatchGodReload detected file change in *.log文件(可能是我的日志文件在本地开发时放在项目代码里,把日志目录移到项目外估计就可以),从而导致项目一直循环重启。这是因为uvicorn[standard]的热加载是依赖于watchdog 模块, watchdog是github上的一个开源项目,采用观察者模式用于监控一个文件目录下的文件和文件夹的变动,从而实现热加载,和默认的--reloader机制不一样造成的。

二、修改Fastapi框架Websocket官方示例实现多房间多人即时通迅

    Fastapi框架Websocket官方示例: https://fastapi.tiangolo.com/advanced/websockets/ 中有一个示例实现多个用户进行即时沟通,但这个示例只能实现多个用户在一起沟通,而不能实现多个用户随便分组随便沟通,因此对这个示例进行了修改实现了多房间多用户的沟通场景。修改的部分以及修改后的代码如下:

from typing import List

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse

app = FastAPI()

html = """
<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>WebSocket Chat</h1>
        <h2>Your ID: <span id="ws-id"></span></h2>
		#添加了room_id参数
		<h2>Room ID: <span id="room_id"></span></h2>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var client_id = Date.now()
            document.querySelector("#ws-id").textContent = client_id;
			
			#随机生成room_id值,当前先分成3个房间,并显示在页面上以方便查看自己当前进入的房间。
			var room_id = "room" + Math.floor(Math.random() * 10) % 3;
			document.querySelector("#room_id").textContent = room_id;
			
			#连接协议就需要改成添加room_id参数的URL了
            #var ws = new WebSocket(`ws://127.0.0.1:8000/ws/${client_id}`);
			var ws = new WebSocket(`ws://127.0.0.1:8010/ws/${room_id}/${client_id}`);
            ws.onmessage = function(event) {
                var messages = document.getElementById('messages')
                var message = document.createElement('li')
                var content = document.createTextNode(event.data)
                message.appendChild(content)
                messages.appendChild(message)
            };
            function sendMessage(event) {
                var input = document.getElementById("messageText")
                ws.send(input.value)
                input.value = ''
                event.preventDefault()
            }
        </script>
    </body>
</html>
"""

class ConnectionManager:
    def __init__(self):
		self.active_connections: Dict = {}

    async def connect(self, room_id:str, websocket: WebSocket):
        await websocket.accept()
        if room_id in self.active_connections:
			self.active_connections[room_id].append(websocket)
		else:
            self.active_connections[room_id] = []	
			self.active_connections[room_id].append(websocket)
		
    def disconnect(self, room_id:str, websocket: WebSocket):
        self.active_connections[room_id].remove(websocket)

    async def send_personal_message(self, message: str, room_id:str, websocket: WebSocket):
        await websocket.send_text(message)

    async def broadcast(self, message: str, room_id:str):
        for connection in self.active_connections[room_id]:
            await connection.send_text(message)

manager = ConnectionManager()

@app.get("/")
async def get():
    return HTMLResponse(html)


@app.websocket("/ws/{room_id}/{client_id}")
async def websocket_endpoint(websocket: WebSocket, room_id:str, client_id: int):
    await manager.connect(room_id, websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.send_personal_message(f"You wrote: {data}", room_id, websocket)
            await manager.broadcast(f"Client #{client_id} says: {data}", room_id)
    except WebSocketDisconnect:
        manager.disconnect(room_id, websocket)
        await manager.broadcast(f"Client #{client_id} left the chat", room_id)

三、以前一点Python相关的笔记保存在此

1989年鬼叔在圣诞节假期决心开发的解释器程序;1991年产生;

Python 优雅:简单、易学、开发效率高。
pycharm:  ctrl+shift+上下将代码上下移动; ctrl+alt_s 打开设置 shift+F10 运行代码。 shift+F6 重命名文件

shift+enter 在下面加一空行
6种值类型:数字/字符/列表/元组/集合/字典
使用type查看数据类型
数据转换:str int float
标识符:变量/类/方法的命名。内容限定/大小写敏感/不可使用关键字
可用[英文,中文,数字,下划线]中文不推荐;数字不能开头;  关键字只有True; False, None三个首字母是大写。

变量命名:见名知义、下划线命名法、英文字母全小写。
取整 // (取整会舍弃小数部分) 指数 ** 运算符右边均可以加=计算
字符串格式化,快速和整数拼接; "%s" % name 拼接符号,占位符。多变量要用()括起来
 %d 占位整数;%f 占位浮点数。  使用m.n来控制数据的宽度和精度。%5d 5位整数,不足在前用空格补足宽度。  %7.2f控制总长7位,2位小数,注此时会进行四舍五入。%.2f则不限制总宽度。  快速写法: f"内容{变量名}"

表达式:有明确执行结果的语句.
input("提示信息:") 接受输入内容。 Python 没有===全等符,只有==双等符
循环 while; for; if elif 也可以。for 临时变量 in 待处理数据集。
函数说明文档,鼠标悬停能查看::param用于解释参数;:return 返回值;
列表、字典等都可叫做 数字容器。列表/元组/字符 属于序列。切片[起; 结;步长]。步长为负反向取
步长为负数时,起始下标和结束下标也需要是反向标
元组和字符串都不可修改;都是产生新的序列。

Python FastAPI 是一个高性能、易于使用、快速编写 API 的 Web 框架。如果你想要在 FastAPI 中增加 WebSocket,可以使用第三方库 fastapi-websocket实现。 首先,你需要在你的项目中安装 fastapi-websocket 库。你可以使用 pip 命令进行安装: ``` pip install fastapi-websocket ``` 安装完成后,在你的 FastAPI 项目中导入 fastapi_websocket 包。然后,你可以通过创建一个 WebSocketEndpoint 类来实现 WebSocket: ```python from fastapi import FastAPI from fastapi_websocket import WebSocket app = FastAPI() class WebSocketEndpoint: def __init__(self, ws: WebSocket): self.ws = ws async def send_message(self, message: str): await self.ws.send_text(message) @app.websocket("/ws") async def websocket_endpoint(ws: WebSocket): websocket = WebSocketEndpoint(ws) await websocket.send_message("Hello, WebSocket!") ``` 在上面的代码中,我们创建了一个 WebSocketEndpoint 类来实现 WebSocket。这个类中包含了一个 send_message 方法,用来发送消息给客户端。然后,在 websocket_endpoint 函数中,我们创建了一个 WebSocketEndpoint 对象,并通过 send_message 方法发送一条消息。 最后,我们将这个 WebSocketEndpoint 函数装饰为 /ws 路径的 WebSocket: ```python @app.websocket("/ws") async def websocket_endpoint(ws: WebSocket): websocket = WebSocketEndpoint(ws) await websocket.send_message("Hello, WebSocket!") ``` 如果你想要了解更多关于 FastAPIfastapi-websocket 的内容,可以查看官方文档:https://fastapi.tiangolo.com/tutorial/websockets/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林戈的IT生涯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值