轻量web服务
介绍
FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 3.6+ 并基于标准的 Python 类型提示。
FastAPI 框架,高性能,易于学习,高效编码,生产可用
文档地址:fastapi
使用
安装
pip install fastapi
pip install uvicorn # 启动方式
构建app
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
启动
uvicorn main:app --reload
或者以代码方式启动:
# main.py
uvicorn.run(app, host="127.0.0.1", port=8000, log_level="debug", loop="asyncio")
访问
在游览器内输入localhost:8000即可
作为saya模块使用并进行交互
此处仅仅为了进行web相关的学习,目前不具备任何实用的功能
模块目录
我尝试使用bootstrap作为web基础,但似乎无法访问到...
static中存放静态文件,templates中存放模板html文件
root@Graia:/home/bot/modules# tree WebApi/
WebApi/
├── __init__.py
├── api.py
├── static
│ ├── css
│ │ ├── bootstrap-grid.css
│ │ ├── ......
│ │ └── bootstrap.rtl.min.css.map
│ └── js
│ ├── @eaDir
│ │ ├── bootstrap.bundle.js@SynoEAStream
│ │ ├── ......
│ │ └── bootstrap.min.js@SynoEAStream
│ ├── ......
│ └── bootstrap.min.js.map
└── templates
├── index.html
├── log.html
└── modules.html
运行方式
fastapi是一个处理请求和发送的循环,无法在已经是个loop的brocast环境中运行,也许有注入运行的方法但是我没有找到;最后使用了线程方式运行一个fastapi服务
def run_api():
app_host = route_path.split(':')
# uvicorn.run(app, host="192.168.0.5", port=8045, log_level="debug", loop="asyncio")
uvicorn.run(app, host=app_host[0], port=int(app_host[1]), log_level="debug", loop="asyncio")
def thred_run_api(saya: Saya):
global apisaya # 此处是为了导入saya模块对象
apisaya = saya
run_loop_thread = Thread(target=run_api)
run_loop_thread.start()
功能&实现
由于是学习目的,目前仅尝试了三个页面
- 一个显示运行时间的主页
- 一个显示运行了多少模块的展示页
- 一个实时显示日志的页面
主要框架我使用了bootstrap页面生成器,然后对着需求修改html和添加js即可
Jinja2模板渲染
需要实现根据请求发送带着信息的html页面,就需要模板来渲染
在html文件上:
<div class="jumbotron">
<h1>
欢迎来到CUAVbot
</h1>
<p>
本次运行时间:{{ now_runtime }}
</p>
<p>
总共运行时间:{{ all_runtime }}
</p>
<p>
</div>
定义路由:
@app.get("/")
async def get_index(request: Request):
return t.TemplateResponse(
"index.html", {
"request": request,
"now_runtime": "显示本次运行时长",
"all_runtime": "显示总运行时长"
}
)
这样每次请求就会发送对应的信息
主页
如上,仅展示标题和运行时间
模块页面
在html上定义表格,并在tbody内等待渲染(safe标签让Jinja2不对内容进行转义,可正常显示html标签)
<table class="table">
<thead>
<tr>
<th>
编号
</th>
<th>
模块
</th>
<th>
作者
</th>
<th>
说明
</th>
</tr>
</thead>
<tbody>{{ sayatable | safe }}</tbody>
定义路由:
@app.get("/modules")
async def get_modules(request: Request):
table_str = ''
modules_temp = "<tr {}><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>"
class_temp = 'class="success"'
channels = apisaya.channels
trager = 1
for channel_name, channel_object in channels.items():
table_str += modules_temp.format(
class_temp if (trager % 2) == 0 else "",
trager,
channel_object._name,
channel_object._author[0],
channel_object._description.replace("\n", "<br />") # 注意用换行标签替换字符
)
trager += 1
return t.TemplateResponse(
"modules.html",
{"request": request, "sayatable": table_str}
)

效果
日志页面
要求实时显示的话对我这种小白就有点复杂,首先是尝试在html中嵌入js来循环请求日志,但失败了
最后是用了websockets方式来循环请求,请求是服务端来发送的,前端只进行接收
首先在html上:
<div id="bot_log">
</div>
......
<script type="text/javascript">
function WebSocketTest(){
var ws = new WebSocket("ws://localhost:8045/log/flash");
ws.onmessage = function (evt){
document.getElementById("bot_log").innerHTML = evt.data;
}
}
WebSocketTest()
</script>
WebSocket.onmessage是定义对接收信息的处理
然后定义一个连接管理对象
class ConnectionManager:
def __init__(self):
# 存放激活的ws连接对象
self.active_connections: List[WebSocket] = []
async def connect(self, ws: WebSocket):
# 等待连接
await ws.accept()
# 存储ws连接对象
self.active_connections.append(ws)
def disconnect(self, ws: WebSocket):
# 关闭时 移除ws对象
self.active_connections.remove(ws)
manager = ConnectionManager()
最后定义路由
log_file_path = os.popen("ls -t ./log/ | head -n 1").read()
@app.websocket("/log/flash")
async def flash_log(websocket: WebSocket):
await manager.connect(websocket)
try:
while True:
log_str = os.popen(f"tail -n 100 ./log/{log_file_path}").read().replace("\n", "<br />")
await websocket.send_text(log_str)
await asyncio.sleep(5)
except WebSocketDisconnect:
manager.disconnect(websocket)
@app.route('/log')
async def get_log(request: Request):
return t.TemplateResponse(
"log.html",
{"request": request, "base_href": base_href}
)
很简陋但是也算完成了需求
一些坑
fastapi的websockets在虚拟环境下运行似乎会报错,不知道是什么原因,但就是一直报错弄的差点放弃了websockets
