一、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 返回值;
列表、字典等都可叫做 数字容器。列表/元组/字符 属于序列。切片[起; 结;步长]。步长为负反向取
步长为负数时,起始下标和结束下标也需要是反向标
元组和字符串都不可修改;都是产生新的序列。