fastapi框架
fastapi
,一个用于构建 API 的现代、快速(高性能)的web框架。
fastapi
是建立在Starlette和Pydantic基础上的,Pydantic是一个基于Python类型提示来定义数据验证、序列化和文档的库。Starlette是一种轻量级的ASGI框架/工具包,是构建高性能Asyncio服务的理性选择。
快速:可与 NodeJS 和 Go 比肩的
极高性能
(归功于Starlette
和Pydantic
),是最快的 Python web 框架之一。高效编码:提高功能开发速度约 200% 至 300%。
更少bug:减少约 40% 的人为(开发者)导致错误。
智能:极佳的编辑器支持。处处皆可自动补全,减少调试时间。
简单:设计的
易于使用和学习
,阅读文档的时间更短。简短:使代码重复最小化。通过不同的参数声明实现丰富功能。
健壮:生产可用级别的代码。还有
自动生成的交互式文档
。
依赖:Python 3.6 及更高版本,FastAPI 站在以下巨人的肩膀之上
View Codefrom datetime import datetime from typing import List, Optional from pydantic import BaseModel class User(BaseModel): # 继承 id: int # id 需要是一个整型,必须字段 name = 'John Doe' # 默认值 类型str signup_ts: Optional[datetime] = None # friends: List[int] = [] # 列表类型 列表里必须是int字段 external_data = { # 模拟web数据 格式 'id': '12', 'signup_ts': '2019-06-01 12:22', 'friends': [1, 2, '3'], } user = User(**external_data) # 星星打撒 按关键字传、 # pydantic自带数据校验及数据转化 将id字符串转化为int型 print(user.id) # > 123 print(repr(user.signup_ts)) # > datetime.datetime(2019, 6, 1, 12, 22) print(user.friends) # > [1, 2, 3] print("user", user) print(user.dict()) # 将user转化为字典
FastApi是站在前人肩膀上,集成了多种框架的优点的新秀框架。它出现的比较晚,2018年底才发布在github上。广泛应用于当前各种前后端分离的项目开发,测试运维自动化以及微服务的场景中。
一、预备知识点
1.1、http协议
1. 什么是请求头请求体,响应头响应体
2. URL地址包括什么
3. get请求和post请求到底是什么
4. Content-Type是什么(前后端分离的重要请求头)
一、简介
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于万维网(WWW:World Wide Web )服务器与本地浏览器之间传输超文本的传送协议。HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。
二、 http协议特性
(1) 基于TCP/IP协议
http协议是基于TCP/IP协议之上的应用层协议。
(2) 基于请求-响应模式
HTTP协议规定,请求从客户端发出,最后服务器端响应该请求并 返回。换句话说,肯定是先从客户端开始建立通信的,服务器端在没有 接收到请求之前不会发送响应
(3) 无状态保存
HTTP是一种不保存状态,即无状态(stateless)协议。HTTP协议 自身不对请求和响应之间的通信状态进行保存。也就是说在HTTP这个 级别,协议对于发送过的请求或响应都不做持久化处理。
使用HTTP协议,每当有新的请求发送时,就会有对应的新响应产 生。协议本身并不保留之前一切的请求或响应报文的信息。这是为了更快地处理大量事务,确保协议的可伸缩性,而特意把HTTP协议设计成 如此简单的。
(4) 短连接
HTTP1.0默认使用的是短连接。浏览器和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。
HTTP/1.1起,默认使用长连接。要使用长连接,客户端和服务器的HTTP首部的Connection都要设置为keep-alive,才能支持长连接。
HTTP长连接,指的是复用TCP连接。多个HTTP请求可以复用同一个TCP连接,这就节省了TCP连接建立和断开的消耗。
三、http请求协议与响应协议
http协议包含由浏览器发送数据到服务器需要遵循的请求协议与服务器发送数据到浏览器需要遵循的请求协议。用于HTTP协议交互的信被为HTTP报文。请求端(客户端)的HTTP报文 做请求报文,响应端(服务器端)的 做响应报文。HTTP报文本身是由多行数据构成的字文本。
# 模拟web应用程序:遵循http协议(先发请求在收响应) import socket # 网络 sock = socket.socket() sock.bind(("127.0.0.1", 8090)) # 绑定端口 sock.listen(5) while 1: conn, addr = sock.accept() # 阻塞等待(客户端没链一值等) 客户端连接 data = conn.recv(1024) # 客户端发送请求 print("客户端发送的请求信息:\n",data) conn.send(b'HTTP/1.1 200 ok\r\nserver:yuan\r\ncontent-type:application/json\r\n\r\n{"user_id":101}') # (get请求) 响应 遵循http响应格式 conn.close() # 关闭管道本次请求结束
一个完整的URL包括:协议、ip、端口、路径、参数
例如: https://www.baidu.com/s?wd=yuan 其中https是协议,www.baidu.com 是IP,端口默认80,/s是路径,参数是wd=yuan
请求方式: get与post请求
GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456. POST方法是把提交的数据放在HTTP包的请求体中.
GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制
响应状态码:状态码的职 是当客户端向服务器端发送请求时, 返回的请求 结果。借助状态码,用户可以知道服务器端是正常 理了请求,还是出 现了 。状态码如200 OK,以3位数字和原因 组成。
Content-Type是什么(前后端分离的重要请求头):
(内容类型,请求的内容类型和响应的内容渲染时的类型)
content-type:application/json 在请求头里 设定请求参数的格式(json)
content-type:application/x-www-form-urlencoded form表单数据被编码为key/value格式发送到服务器
content-type:text/html : HTML格式
content-type:text/plain :纯文本格式
等等。。。
1.2、api接口
在开发Web应用中,有两种应用模式:
-
前后端不分离[客户端看到的内容和所有界面效果都是由服务端提供出来的。]
-
前后端分离【把前端的界面效果(html,css,js分离到另一个服务端,python服务端只需要返回数据即可)】
前端形成一个独立的网站,服务端构成一个独立的网站
应用程序编程接口(Application Programming Interface,API接口),就是应用程序对外提供了一个操作数据的入口,这个入口可以是一个函数或类方法,也可以是一个url地址或者一个网络地址。当客户端调用这个入口,应用程序则会执行对应代码操作,给客户端完成相对应的功能。
当然,api接口在工作中是比较常见的开发内容,有时候,我们会调用其他人编写的api接口,有时候,我们也需要提供api接口给其他人操作。由此就会带来一个问题,api接口往往都是一个函数、类方法、或者url或其他网络地址,不断是哪一种,当api接口编写过程中,我们都要考虑一个问题就是这个接口应该怎么编写?接口怎么写的更加容易维护和清晰,这就需要大家在调用或者编写api接口的时候要有一个明确的编写规范!!!
为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们都需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少客户端和服务端双方之间的合作成本。
目前市面上大部分公司开发人员使用的接口实现规范主要有:restful、RPC。
REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中。
RESTful是一种专门为Web 开发而定义API接口的设计风格,尤其适用于前后端分离的应用模式中。
关键:面向资源开发
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。
而对于数据资源分别使用POST、DELETE、GET、UPDATE等请求动作来表达对数据的增删查改。
请求方法 | 请求地址 | 后端操作 |
---|---|---|
POST | /student/ | 增加学生 |
GET | /student/ | 获取所有学生 |
GET | /student/1 | 获取id为1的学生 |
PUT | /student/1 | 修改id为1的学生 |
DELETE | /student/1 | 删除id为1的学生 |
restful规范是一种通用的规范,不限制语言和开发框架的使用。事实上,我们可以使用任何一门语言,任何一个框架都可以实现符合restful规范的API接口。
二、quick start
简单案例
安装
pip install fastapi
你还会需要一个 ASGI 服务器,生产环境可以使用
pip install uvicorn
代码
from fastapi import FastAPI # FastAPI 是一个为你的 API 提供了所有功能的 Python 类。 app = FastAPI() # 这个实例将是创建你所有 API 的主要交互对象。这个 app 同样在如下命令中被 uvicorn 所引用 # async用来声明一个函数为异步函数,异步函数的特点就是能在函数执行过程中被挂起,去执行其他异步函数,等挂起条件消失后再回来执行 @app.get("/") async def root(): return {"message": "Hello yuan"}
通过以下命令运行服务器:
uvicorn main:app --reload INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) INFO: Started reloader process [73408] INFO: Started server process [73408] INFO: Waiting for application startup INFO: Application startup complete
也可以直接运行:
if __name__ == '__main__': import uvicorn uvicorn.run("main:app", host="127.0.0.1", port=8080, debug=True, reload=True)
(1)导入 FastAPI。 (2)创建一个 app 实例。 (3)编写一个路径操作装饰器(如 @app.get("/"))。 (4)编写一个路径操作函数(如上面的 def root(): ...) (5)定义返回值 (6)运行开发服务器(如 uvicorn main:app --reload)
此外,fastapi有着非常棒的交互式 API 文档,这一点很吸引人。
跳转到
案例:
from fastapi import FastAPI import uvicorn app = FastAPI() @app.get("/") async def home(): return {"user_id": 1002} @app.get("/shop") async def shop(): return {"shop": "商品信息"} if __name__ == '__main__': uvicorn.run("03 fastapi quickstart:app", port=8080, debug=True, reload=True)
三、路径操作
(学习web框架顺序:路由 路由函数(请求响应)数据库(orm 模版引擎 中间件))
路径操作装饰器
fastapi支持各种请求方式:
@app.get()
@app.post()
@app.put()
@app.patch()
@app.delete()
@app.options()
@app.head()
@app.trace()
04 路径操作装饰器方法.py
from typing import Union from fastapi import FastAPI app = FastAPI() @app.get("/get") def get_test(): return {"method": "get方法"} @app.post("/post") def post_test(): return {"method": "post方法"} @app.put("/put") def put_test(): return {"method": "put方法"} @app.delete("/delete") def delete_test(): return {"method": "delete方法"} if __name__ == '__main__': uvicorn.run("04 路径操作装饰器方法:app", port=8080, debug=True, reload=True)
路径操作装饰器参数:
@app.post( "/items/{item_id}", response_model=Item, status_code=status.HTTP_200_OK, tags=["AAA"], summary="this is summary", description="this is description", response_description= "this is response_description", deprecated=False, )
05 路径操作装饰器方法的参数.py
from typing import Union from fastapi import FastAPI import uvicorn app = FastAPI() @app.post("/items", tags=["标题:这是items测试接口"], summary="概述:this is items测试 summary", description="总结描述:this is items测试 description...", response_description="详细描述:this is items测试 response_description...", deprecated=True, ) def test(): return {"items": "items数据"} if __name__ == '__main__': uvicorn.run("05 路径操作装饰器方法的参数:app", port=8080, debug=True, reload=True)
include_router
将不同子应用路由做一个分发和解藕
main.py:
from typing import Union from fastapi import FastAPI import uvicorn from apps import app01, app02 app = FastAPI() app.include_router(app01, prefix="/app01", tags=["第一章节:商城接口", ]) # 将子路由加入到总路由中 app.include_router(app02, prefix="/app02", tags=["第二章节:用户中心接口", ]) if __name__ == '__main__': uvicorn.run("main:app", host="127.0.0.1", port=8080, debug=True, reload=True)
与main.py同级目录apps不同子应用:
# __init__.py from .app01 import app01 from .app02 import app02
app01.py:
from fastapi import APIRouter app01 = APIRouter() @app01.get("/shop/food") def shop_food(): return {"shop": "food"} @app01.get("/shop/bed") def shop_food(): return {"shop": "bed"}
app02.py:
from fastapi import APIRouter app02 = APIRouter() @app02.get("/shop/food") def shop_food(): return {"shop": "food"} @app02.get("/shop/bed") def shop_food(): return {"shop": "bed"}
四、请求与响应
main.py:
from fastapi import FastAPI import uvicorn from fastapi.staticfiles import StaticFiles from apps.app01 import app01 from apps.app02 import app02 from apps.app03 import app03 from apps.app04 import app04 from apps.app05 import app05 from apps.app06 import app06 from apps.app07 import app07 app = FastAPI() app.mount("/static", StaticFiles(directory="statics")) # 请求静态文件 app.include_router(app01, tags=["01 路径参数"]) app.include_router(app02, tags=["02 查询参数"]) app.include_router(app03, tags=["03 请求体数据"]) app.include_router(app04, tags=["04 form表单数据"]) app.include_router(app05, tags=["05 文件上传"]) app.include_router(app06, tags=["06 Request对象"]) app.include_router(app07, tags=["07 响应参数"]) if __name__ == '__main__': uvicorn.run("main:app", port=8090, debug=True, reload=True)
4.1、路径参数
(1)基本用法
以使用与 Python 格式化字符串相同的语法来声明路径"参数"或"变量":
@app.get("/user/{user_id}") def get_user(user_id): print(user_id, type(user_id)) return {"user_id": user_id}
路径参数 user_id
的值将作为参数 user_id
传递给你的函数。
(2)有类型 的路径参数(type hints)
你可以使用标准的 Python 类型标注为函数中的路径参数声明类型。
@app.get("/user/{user_id}") def get_user(user_id: int): print(user_id, type(user_id)) return {"user_id": user_id}
在这个例子中,user_id
被声明为 int
类型。
这将为你的函数提供编辑器支持,包括错误检查、代码补全等等。
(3)注意顺序
在创建路径操作时,你会发现有些情况下路径是固定的。
比如 /users/me
,我们假设它用来获取关于当前用户的数据.
然后,你还可以使用路径 /user/{username}
来通过用户名 获取关于特定用户的数据。
由于路径操作是按顺序依次运行的 匹配成功后就不会往下在走了,你需要确保路径 /user/me
声明在路径 /user/{username}
之前:
@app.get("/user/me") async def read_user_me(): return {"username": "the current user"} @app.get("/user/{username}") async def read_user(username: str): # username: str ,useername是str类型实例化的一个对象 在此是个参数 return {"username": username}
否则,/user/{username}
的路径还将与 /user/me
相匹配,"认为"自己正在接收一个值为 "me"
的 username
参数 所以/user/me会永远不会执行被覆盖掉了。
app01.py
from fastapi import APIRouter app01 = APIRouter() # 路由匹配顺序 # root用户 @app01.get("/user/1") def get_user(): return { "user_id": "root user" } @app01.get("/user/{id}") def get_user(id): # id = 1 print("id", id, type(id)) return { "user_id": id } @app01.get("/articles/{id}") def get_article(id: int): # id = 1 print("id", id, type(id)) return { "article_id": id }
4.2、查询参数(请求参数)
路径函数中声明不属于路径参数的其他函数参数时,它们将被自动解释为"查询字符串"参数,就是 url问号? 之后用&
分割的 key-value 键值对。
@app.get("/jobs/{kd}") async def search_jobs(kd: str, city: Union[str, None] = None, xl: Union[str, None] = None): # 有默认值即可选,否则必选 if city or xl: return {"kd": kd, "city": city, "xl": xl} return {"kd": kd}
在这个例子中,函数参数 city
和xl
是可选的,并且默认值为 None
。
from fastapi import APIRouter from typing import Union, Optional app02 = APIRouter() # async用来声明一个函数为异步函数,异步函数的特点就是能在函数执行过程中被挂起,去执行其他异步函数,等挂起条件消失后再回来执行 @app02.get("/jobs/{kd}") async def get_jobs(kd: str, xl: Union[str, None] = None, gj: Optional[str] = None): # 没有默认参数即前端必须输入 # 基于kd, xl, gj数据库查询岗位信息 return { "kd": kd, "xl": xl, "gj": gj, }
自python3.5开始,PEP484为python引入了类型注解(type hints),typing的主要作用有:
类型检查,防止运行时出现参数、返回值类型不符。
作为开发文档附加说明,方便使用者调用时传入和返回参数类型。
模块加入不会影响程序的运行不会报正式的错误,pycharm支持typing检查错误时会出现黄色警告。
type hints
主要是要指示函数的输入和输出的数据类型,数据类型在typing 包中,基本类型有str list dict等等,
Union 是当有多种可能的数据类型时使用,比如函数有可能根据不同情况有时返回str或返回list,那么就可以写成Union[list, str] Optional 是Union的一个简化, 当 数据类型中有可能是None时,比如有可能是str也有可能是None,则Optional[str], 相当于Union[str, None]
4.3、请求体数据
当你需要将数据从客户端(例如浏览器)发送给 API 时,你将其作为「请求体」发送。请求体是客户端发送给 API 的数据。响应体是 API 发送给客户端的数据。
FastAPI 基于 Pydantic
,Pydantic
主要用来做类型强制检查(校验数据)。前端数据传到数据库时一定有数据校验,不符合类型要求就会抛出异常。
对于 API 服务,支持类型检查非常有用,会让服务更加健壮,也会加快开发速度比drf快10倍,因为开发者再也不用自己写一行一行的做类型检查。
安装上手pip install pydantic
from typing import Union, List, Optional from fastapi import FastAPI from pydantic import BaseModel, Field, ValidationError, validator import uvicorn from datetime import date class Addr(BaseModel): province: str city: str # 模拟网站登录注册 继承BaseModel才有数据校验的功能 class User(BaseModel): # 这是User类型和int类型一样,这个是自定义而已 # user类型 name = 'root' # 加个默认值 age: int = Field(default=0, lt=100, gt=0) # int类型 并加入默认值 和 一个范围约束 birth: Optional[date] = None # Optional等同于Union,默认值None friends: List[int] = [] # [1,2] description: Union[str, None] = None # 默认为None # addr: Union[Addr, None] = None # 类型嵌套 # 对name属性(数据库中的字段)进行自定义校验 @validator('name') def name_must_alpha(cls, v): assert v.isalpha(), 'name must be alpha' # 进行一个断言 alpha字母 return v class Data(BaseModel): # 类型嵌套 # data中的user类型是个列表嵌套了User类型,列表里可以有多个user单这个user必须按照User格式来传参 users: List[User] app = FastAPI() @app.post("/user/") async def create_user(data: User): # data: User这个User类型是我们自己定义的 传参一定要按照User这个类型下的属性格式来 # 添加数据库 return user @app.post("/data/") async def create_data(data: Data): # data: Data这个data类型是我们自己定义的 传参一定要按照data这个类型下的属性格式来 # 添加数据库 return data if __name__ == '__main__': try: User(name="",...) except ValidationError as e: print(e.json())
知识点:数据校验,格式自动转化,默认值和范围约束,类型嵌套
通过postman将数据按照User类型格式 请求传入到程序中,程序会自动校验,打印请求结果如下。如果传入的格式不对,程序会自动进行数据格式转化成对的,再如果传入的数据格式没法转换此时才会报错
测试结果:(请求体内容和格式:User类型下的属性格式)
{ "name": "rain", "age": 32, "birth": "2022-09-29", "friends": [], "description": "最帅的讲fastapi的老师" }
Data类型:
{ "data":[ { "name": "rain", "age": 32, "birth": "2022-09-29", "friends": [], "description": "最帅的讲fastapi的老师" }, { "name": "yuan", "age": 12, "birth": "2022-09-29", "friends": [], "description": "最帅的讲fastapi的老师" } ] }
必填和选填的区别:和声明查询参数时一样,当一个模型属性具有默认值时,它不是必需的。否则它是一个必需属性(前端必须得传此字段(属性))。将默认值设为 None
可使其成为可选属性。
FastAPI 会自动将定义的模型类转化为JSON Schema
,Schema 成为 OpenAPI 生成模式的一部分,并显示在 API 交互文档中,查看 API 交互文档如下,该接口将接收application/json
类型的参数。
FastAPI 支持同时定义 Path 参数、Query 参数和请求体参数,FastAPI 将会正确识别并获取数据。
参数在 url 中也声明了,它将被解释为 path 参数
参数是单一类型(例如int、float、str、bool等),它将被解释为 query 参数
参数类型为继承 Pydantic 模块的
BaseModel
类的数据模型类,则它将被解释为请求体参数
4.4、form表单数据
在 OAuth2 规范的一种使用方式(密码流)中,需要将用户名、密码作为表单字段发送,content-type:application/x-www-form-urlencoded ,form表单数据被编码为key/value格式发送到服务器,而不是 JSON。
FastAPI 可以使用Form组件来接收表单数据,需要先使用pip install python-multipart
命令进行安装。
pip install python-multipart
from fastapi import FastAPI, Form app = FastAPI() @app.post("/regin") def regin(username: str = Form(max_length=16, min_length=8, regex='[a-zA-Z]'), password: str = Form(max_length=16, min_length=8, regex='[0-9]')):# 对from表单校验设置 print(f"username:{username},password:{password}") return {"username": username}
from fastapi import APIRouter from fastapi import Form app04 = APIRouter() @app04.post("/regin") async def reg(username: str = Form(), password: str = Form()): # username: str = Form(), password: str = Form() 设置了str = Form(),前端传参body必须是content-type:application/x-www-form-urlencoded格式 后端代码才会识别到 print(f"username: {username},password:{password}") # 注册,实现数据库的添加操作 return { "username": username }
4.5、文件上传
给服务器上传文件
请求体的新格式:Content-Type: multipart/form-data;
from fastapi import FastAPI, File, UploadFile from typing import List app = FastAPI() # 上传单个文件 # file: bytes = File():字节流文件对象,适合小文件上传 @app.post("/files/") async def create_file(file: bytes = File()): print("file:", file) return {"file_size": len(file)} # 上传多个文件 files: List[bytes] = File() @app.post("/multiFiles/") async def create_files(files: List[bytes] = File()): return {"file_sizes": [len(file) for file in files]} # file: UploadFile:适合大文件上传 @app.post("/uploadFile/") async def create_upload_file(file: UploadFile): with open(f"{file.filename}", 'wb') as f:# 打开文件 for chunk in iter(lambda: file.file.read(1024), b''): f.write(chunk) # 将上传的文件chunk写入到文件f中 return {"filename": file.filename} # 上传多个文件 @app.post("/multiUploadFiles/") async def create_upload_files(files: List[UploadFile]): return {"filenames": [file.filename for file in files]}
4.6、Reqeust对象
有些情况下我们希望能直接访问Request对象。例如我们在路径操作函数中想获取客户端的IP地址,需要在函数中声明Request类型的参数,FastAPI 就会自动传递 Request 对象给这个参数,我们就可以获取到 Request 对象及其属性信息,例如 header、url、cookie、session 等。
from fastapi import Request @app.get("/items") # async def items(request): # 此时request就是普通的查询对象 参数 async def items(request: Request): # 有类型的参数 此时request是Request类实例化的对象 在此是个参数 return { "请求URL:": request.url, "请求ip:": request.client.host, "请求宿主:": request.headers.get("user-agent"), "cookies": request.cookies, }
4.7、请求静态文件
在 Web 开发中,需要请求很多静态资源文件(不是由服务器生成的文件),如 css/js 和图片文件等。
需要服务器产生的文件是动态文件。
main.py
from fastapi.staticfiles import StaticFiles app = FastAPI() app.mount("/static",StaticFiles(directory="static")) # /static名字随便起 ,directory="static" 设置/static为静态文件
4.8、响应模型相关参数
(1)response_model(控制响应体结构)
前面写的这么多路径函数最终 return 的都是自定义结构的字典,FastAPI 提供了 response_model 参数(路径操作上的参数),声明 return 响应体的模型
# 路径操作 @app.post("/items/", response_model=Item) # 路径函数 async def create_item(item: Item): ...
response_model 是路径操作的参数,并不是路径函数的参数哦
FastAPI将使用response_model
进行以下操作:
-
将输出数据转换为response_model中声明的数据类型。
-
验证数据结构和类型
-
将输出数据限制为该model定义的
-
添加到OpenAPI中
-
在自动文档系统中使用。
你可以在任意的路径操作中使用 response_model
参数来声明用于响应的模型
案例:
-
注册功能
-
输入账号、密码、昵称、邮箱,注册成功后返回个人信息
from typing import Union from fastapi import FastAPI from pydantic import BaseModel, EmailStr app = FastAPI() # 用户输入参数 校验 class UserIn(BaseModel): username: str password: str email: EmailStr full_name: Union[str, None] = None # 输出 对字段进行过滤 class UserOut(BaseModel): username: str email: EmailStr full_name: Union[str, None] = None # 用户接收 @app.post("/user/", response_model=UserOut) # 其次return user 需要通过响应模型UserOut进行过滤 async def create_user(user: UserIn): # 首先对用户输入字段按照UserIn类型进行校验 # 存到数据库 return user # 响应按照response_model=UserOut这个模型过滤参数
(2)response_model_exclude_unset
通过上面的例子,我们学到了如何用response_model控制响应体结构,但是如果它们实际上没有存储,则可能要从结果中忽略它们。例如,如果model在NoSQL数据库中具有很多可选属性,但是不想发送很长的JSON响应,其中包含默认值。
案例:
from typing import List, Union from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str description: Union[str, None] = None price: float tax: float = 10.5 tags: List[str] = [] items = { "foo": {"name": "Foo", "price": 50.2}, "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []}, } # 未设置response_model_exclude_unset参数 @app.post("/user/", response_model=UserOut) # 其次return user 需要通过响应模型UserOut进行过滤 async def create_user(user: UserIn): # 首先对用户输入字段按照UserIn类型进行校验 # 存到数据库 return user # 响应按照response_model=UserOut这个模型过滤参数 # 设置response_model_exclude_unset参数=True: @app07.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True) async def read_item(item_id: str): return items[item_id]
请求:http://127.0.0.1:8080/items/foo
请求只输入了items中的foo值,未设置unset参数的时候,会响应所有的需要的字段(属性)
不设置response_model_exclude_unset参数:响应所有需要默认传的Item中的字段(属性)
{ "name": "Foo", "description": null, "price": 50.2, "tax": 10.5, "tags": [] }
设置unset参数=true:只需要响应foo中传的字段(属性)
{ "name": "Foo", "price": 50.2 }
使用路径操作装饰器的 response_model
参数来定义响应模型,特别是确保私有数据被过滤掉。使用 response_model_exclude_unset
来仅返回显式设定的值。 除了response_model_exclude_unset
以外,还有response_model_exclude_defaults
和response_model_exclude_none
,我们可以很直观的了解到他们的意思,不返回是默认值的字段和不返回是None的字段。
(3)INCLUDE和EXCLUDE
# response_model_exclude 响应中排出设置的字段 @app.get("/items/{item_id}", response_model=Item, response_model_exclude={"description"}, ) async def read_item(item_id: str): return items[item_id] # response_model_include 只响应设置的字段 @app.get("/items/{item_id}", response_model=Item, response_model_include={"name", "price"}, ) async def read_item(item_id: str): return items[item_id]
五、jinja2模板(用在前后端不分离模式)
要了解jinja2,那么需要先理解模板的概念。模板在Python的web开发中⼴泛使⽤,它能够有效的将业务逻辑和页⾯逻辑分开,使代码可读性增强、并且更加容易理解和维护。 模板简单来说就是⼀个其中包涵占位变量表⽰动态的部分的⽂件,模板⽂件在经过动态赋值后,返回给⽤户。(晦涩难懂)
jinja2是Flask作者开发的⼀个模板系统,起初是仿django模板的⼀个模板引擎,为Flask提供模板⽀持,由于其灵活,快速和安全等优点被⼴泛使⽤。
在jinja2中,存在三种语法:(在模版文件中用的动态语法)
变量取值 {{ }}
控制结构 {% %}
5.1、jinja2 的变量渲染
Main.py
:
from fastapi import FastAPI, Request from fastapi.templating import Jinja2Templates import uvicorn app = FastAPI() # 实例化 FastAPI对象 templates = Jinja2Templates(directory="templates") # 实例化Jinja2对象,并将文件夹路径设置为以templates命令的文件夹 @app.get('/') def hello(request: Request): # 响应模版 return templates.TemplateResponse( 'index.html', # 模版文件 { # data 'request': request, # 注意,返回模板响应时,必须有request键值对,且值为Request请求对象 'user': 'yuan', "books": ["金梅", "聊斋", "剪灯新话", "国色天香"], "booksDict": { "金梅": {"price": 100, "publish": "苹果出版社"}, "聊斋": {"price": 200, "publish": "橘子出版社"}, } } ) if __name__ == '__main__': uvicorn.run("main:app", port=8080, debug=True, reload=True)
index.html:模版文件(包含占位符的html文件)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>{{ user}}</h1> <p>{{ books.0 }}</p> <p>{{ books.1 }}</p> <p>{{ books.2 }}</p> <p>{{ books.3 }}</p> <p>{{ booksDict.金梅.price }}</p> </body> </html>
5.2、jinja2 的过滤器
变量可以通过“过滤器”进⾏修改,过滤器可以理解为是jinja2⾥⾯的内置函数和字符串处理函数。常⽤的过滤器有:
过滤器名称 | 说明 |
---|---|
capitialize | 把值的⾸字母转换成⼤写,其他⼦母转换为⼩写 |
lower | 把值转换成⼩写形式 |
title | 把值中每个单词的⾸字母都转换成⼤写 |
trim | 把值的⾸尾空格去掉 |
striptags | 渲染之前把值中所有的HTML标签都删掉 |
join | 拼接多个值为字符串 |
round | 默认对数字进⾏四舍五⼊,也可以⽤参数进⾏控制 |
safe | 渲染时值不转义 |
那么如何使⽤这些过滤器呢?只需要在变量后⾯使⽤管道(|)分割,多个过滤器可以链式调⽤,前⼀个过滤器的输出会作为后⼀个过滤 器的输⼊。
{{ 'abc'| captialize }} # Abc {{ 'abc'| upper }} # ABC {{ 'hello world'| title }} # Hello World {{ "hello world"| replace('world','yuan') | upper }} # HELLO YUAN {{ 18.18 | round | int }} # 18 <p>用户名:{{ user|upper }}</p>
5.3、jinja2 的控制结构
模版语法的控制结构是为了是否展示用,python中的控制语句是为了是否执行代码块用
5.3.1、分支控制
jinja2中的if语句类似与Python的if语句,它也具有单分⽀,多分⽀等多种结构,不同的是,条件语句不需要使⽤冒号结尾,⽽结束控制语句,需要使⽤endif关键字
{% if age > 18 %} <p>成年区</p> {% else %} <p>未成年区</p> {% endif %}
5.3.2、循环控制
jinja2中的for循环⽤于迭代Python的数据类型,包括列表,元组和字典。在jinja2中不存在while循环。
{% for book in books %} <p>{{ book }}</p> {% endfor %}
模版文件index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p>用户名:{{ user|upper }}</p> <p>年龄:{{ age }}</p> <p>四大遗珠: {{ books }}</p> <p><1>: {{ books.0 }}</p> <p><2>: {{ books.1 }}</p> <p><3>: {{ books.2 }}</p> <p><4>: {{ books.3 }}</p> <ul> <li> {{ books.0 }}</li> <li> {{ books.1 }}</li> <li> {{ books.2 }}</li> <li> {{ books.3 }}</li> </ul> <p>姓名:{{ info.name }}</p> <p>年龄:{{ info.age }}</p> <p>性别:{{ info.gender }}</p> <p>pai : {{ pai|round(3) }}</p> <p>pai : {{ pai|round(5) }}</p> <p>影视区</p> {% if age > 18 %} <ul> {% for movie in movies.chengnian %} <li>{{ movie }}</li> {% endfor %} </ul> {% else %} <ul> {% for movie in movies.qingshaonian %} <li>{{ movie }}</li> {% endfor %} </ul> {% endif %} <hr> <ul> {% for book in books %} {% if book.price >= 200 %} <li>{{ book.title }}</li> {% endif %} {% endfor %} </ul> </body> </html>
六、ORM操作
ORM操作是通过用python类和对象来模拟sql的原生操作
在大型的web开发中,我们肯定会用到数据库操作,那么FastAPI也支持数据库的开发,你可以用 PostgreSQL、MySQL、 SQLite Oracle 等。本文用SQLite为例。我们看下在fastapi是如何操作设计数据库的。
fastapi是一个很优秀的框架,但是缺少一个合适的orm,官方代码里面使用的是sqlalchemy,Tortoise ORM 是受 Django 启发的易于使用的异步(asyncio) ORM (对象关系映射器),我们使用Tortoise ORM。注意在Tortoise ORM下面必须操作异步支持(async,await是一个类似组合,await在异步操作数据前加)
Tortoise ORM 目前支持以下
PostgreSQL >= 9.4(使用
asyncpg
)SQLite(使用
aiosqlite
)MySQL/MariaDB(使用
aiomysql
或使用
6.1、创建模型
以选课系统为例:
models.py
from tortoise.models import Model from tortoise import fields class Clas(Model): name = fields.CharField(max_length=255, description='班级名称') class Teacher(Model): id = fields.IntField(pk=True) name = fields.CharField(max_length=255, description='姓名') tno = fields.IntField(description='账号') pwd = fields.CharField(max_length=255, description='密码') class Student(Model): id = fields.IntField(pk=True) # pk主键 sno = fields.IntField(description='学号') pwd = fields.CharField(max_length=255, description='密码') name = fields.CharField(max_length=255, description='姓名') # 一对多ForeignKeyField 反向查询related_name clas = fields.ForeignKeyField('models.Clas', related_name='students') # 多对多ManyToManyField 反向查询related_name courses = fields.ManyToManyField('models.Course', related_name='students',description='学生选课表') class Course(Model): id = fields.IntField(pk=True) name = fields.CharField(max_length=255, description='课程名') # 一对多 teacher = fields.ForeignKeyField('models.Teacher', related_name='courses', description='课程讲师')
6.2、aerich迁移工具
main.py
import uvicorn from fastapi import FastAPI from tortoise.contrib.fastapi import register_tortoise from settings import TORTOISE_ORM app = FastAPI() # register_tortoise该方法会在fastapi启动时触发,内部通过传递进去的app对象,监听服务启动和终止事件 # 当检测到启动事件时,会初始化Tortoise对象,如果generate_schemas为True则还会进行数据库迁移 # 当检测到终止事件时,会关闭连接 register_tortoise( app, config=TORTOISE_ORM, # settings文件里 # generate_schemas=True, # 如果数据库为空,则自动生成对应表单,生产环境不要开 # add_exception_handlers=True, # 生产环境不要开,会泄露调试信息 ) if __name__ == '__main__': uvicorn.run('main:app', host='127.0.0.1', port=8000, reload=True, debug=True, workers=1)
settings.py
TORTOISE_ORM = { 'connections': { 'default': { # 'engine': 'tortoise.backends.asyncpg', PostgreSQL 'engine': 'tortoise.backends.mysql', # MySQL or Mariadb 'credentials': { 'host': '127.0.0.1', 'port': '3306', 'user': 'root', 'password': 'yuan0316', 'database': 'fastapi', 'minsize': 1, 'maxsize': 5, 'charset': 'utf8mb4', "echo": True } }, }, 'apps': { 'models': { 'models': ['apps.models', "aerich.models"], # apps.models 模型类路径,迁移哪个模型类就把模型类路径放这 # # aerich.models这是自带的模型类必须有 'default_connection': 'default', } }, 'use_tz': False, 'timezone': 'Asia/Shanghai' }
aerich
是一种
pip install aerich
1. 初始化配置,只需要使用一次
aerich init -t settings.TORTOISE_ORM # TORTOISE_ORM配置的位置)
初始化完会在当前目录生成一个文件:pyproject.toml和一个文件夹:migrations
pyproject.toml
:保存配置文件路径,低版本可能是aerich.ini
migrations
:存放迁移文件
2. 初始化数据库,一般情况下只用一次
aerich init-db
此时数据库中就有相应的表格
如果
TORTOISE_ORM
配置文件中的models
改了名,则执行这条命令时需要增加--app
参数,来指定你修改的名字
3. 更新模型并进行迁移
修改model类,重新生成迁移文件,比如添加一个字段
class Admin(Model): ... xxx = fields.CharField(max_length=255) aerich migrate [--name] (标记修改操作) # aerich migrate --name add_column # 给此次操作添加名称
迁移文件名的格式为 {version_num}{datetime}{name|update}.json。
注意,此时sql并没有执行,数据库中admin表中没有xxx字段
4. 重新执行迁移,写入数据库
aerich upgrade
5. 回到上一个版本
aerich downgrade
6. 查看历史迁移记录
aerich history
6.3、api接口与restful规范
api接口
应用程序编程接口(Application Programming Interface,API接口),就是应用程序对外提供了一个操作数据的入口,这个入口可以是一个函数或类方法,也可以是一个url地址或者一个网络地址。当客户端调用这个入口,应用程序则会执行对应代码操作,给客户端完成相对应的功能。
当然,api接口在工作中是比较常见的开发内容,有时候,我们会调用其他人编写的api接口,有时候,我们也需要提供api接口给其他人操作。由此就会带来一个问题,api接口往往都是一个函数、类方法、或者url或其他网络地址,不管是哪一种,当api接口编写过程中,我们都要考虑一个问题就是这个接口应该怎么编写?接口怎么写的更加容易维护和清晰,这就需要大家在调用或者编写api接口的时候要有一个明确的编写规范!!!
restful规范
为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们都需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少客户端和服务端双方之间的合作成本。
目前市面上大部分公司开发人员使用的接口实现规范主要有:restful、RPC。
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。
简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。
GET用来获取资源
POST用来新建资源
PUT用来更新资源
DELETE用来删除资源。
只要API程序遵循了REST风格,那就可以称其为RESTful API。目前在前后端分离的架构中,前后端基本都是通过RESTful API来进行交互。
例如,我们现在要编写一个选课系统的接口,我们可以查询对一个学生进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们Web服务端交互的方式和路径。
而对于数据资源分别使用POST、DELETE、GET、UPDATE等请求动作来表达对数据的增删查改。
GET | /students | 获取所有学生 |
---|---|---|
请求方法 | 请求地址 | 后端操作 |
GET | /students | 获取所有学生 |
POST | /students | 增加学生 |
GET | /students/1 | 获取编号为1的学生 |
PUT | /students/1 | 修改编号为1的学生 |
DELETE | /students/1 | 删除编号为1的学生 |
6.4、选课系统接口开发
main.py:
import uvicorn from fastapi import FastAPI from tortoise.contrib.fastapi import register_tortoise from settings import TORTOISE_ORM from api.student import student_api app = FastAPI() app.include_router(student_api, prefix="/student", tags=["选课系统的学生接口"]) # 总路由 # fastapi一旦运行,register_tortoise已经执行,实现监控 register_tortoise( app=app, config=TORTOISE_ORM, ) if __name__ == '__main__': uvicorn.run('main:app', host='127.0.0.1', port=8010, reload=True, debug=True, workers=1)
先把接口定义出来:
from fastapi import APIRouter from models import * from pydantic import BaseModel, validator from typing import List, Union from fastapi.templating import Jinja2Templates from fastapi import Request from fastapi.exceptions import HTTPException student_api = APIRouter() @student_api.get("/") async def getAllStudent(): return {"操作":"查看所有学生"} @student_api.post("/") async def addStudent(student_in: StudentIn): return student @student_api.get("/{student_id}") async def getOneStudent(student_id: int): return {"操作":"添加一个学生"} @student_api.put("/{student_id}") async def updateStudent(student_id: int, student_in: StudentIn): return {"操作":"修改一个学生"} @student_api.delete("/{student_id}") async def deleteStudent(student_id: int): return {"操作":"删除一个学生"}
然后在根据具体业务开发接口:
api/student.py
from fastapi import APIRouter from models import * from pydantic import BaseModel, validator from typing import List, Union from fastapi.templating import Jinja2Templates from fastapi import Request from fastapi.exceptions import HTTPException student_api = APIRouter() @student_api.get("/") async def getAllStudent(): # async 异步任务 咱们的orm必须在异步任务下操作 异步操作数据库前加await(async,await是一个类似组合,await在异步操作数据前加) # 异步/同步区别,异步就是一种通信的并发形式,当程序中没有async,await时此时时同步操作,排队等一个一个操作执行完。async,await实现了一个协程并发,一旦一个请求有堵塞,就会把任务加到任务队列中,不管任务有多少个在有限的cpu资源下,执行队列遇到阻塞就执行另外一个,相当于一个cpu为多个任务协程并发起来,不至于同步等待。await 是加在有数据操作的前面 # (1) 查询所有 all方法 返回一个集合对象 students = await Student.all() # Queryset: [Student(),Student(),Student()] # 得到一个Queryset新数据类型(集合对象) # print("students", students) # 可以进行循环查询 # for stu in students: # print(stu.name, stu.sno) # 可以进行获取属性值 # print(students[0].name) # (2) 过滤查询 filter 返回一个集合对象 # students = await Student.filter(name="rain") # Queryset: [Student(),Student(),Student()] # students = await Student.filter(clas_id=14) # Queryset: [Student(),Student(),Student()] # print("students", students) # (3) 过滤查询 get方法:返回模型类型对象 # stu = await Student.filter(id=6) # Queryset: [Student(),] # print(stu[0].name) # stu = await Student.get(id=6) # Student() # print(stu.name) # (4) 模糊查询__gt __range __in # stus = await Student.filter(sno__gt=2001) # stus = await Student.filter(sno__range=[1, 10000]) # stus = await Student.filter(sno__in=[2001, 2002]) # print(stus) # [<Student: 7>, <Student: 8>] # (5) values查询 # stus = await Student.filter(sno__range=[1, 10000]) # Queryset:[Student(),Student(),Student(),...] # stus = await Student.all().values("name", "sno") # [{"name":“rain”,"sno":2001},{},{},...] # print(stus) #查询出来有个学生就有几个字典,values有个值就字典就有几个键值对 # (6) 一对多查询 (班级和学生) alvin = await Student.get(name="alvin") # 查一个学生 print(alvin.name) print(alvin.sno) # 查一个学生的班级信息(一对多) print(await alvin.clas.values("name")) # {'name': '计算机科学与技术2班'} students = await Student.all().values("name", "clas__name") # 查询多个学生的班级信息 # 多对多查询 (课程和学生) print(await alvin.courses.all().values("name", "teacher__name")) # 查询学生的所有课程信息 students = await Student.all().values("name", "clas__name", "courses__name") return students @student_api.get("/index.html") async def getAllStudent(request: Request): # 模版语法 定义一个模版文件夹 templates = Jinja2Templates(directory="templates") students = await Student.all() # Queryset:[Student(),Student(),...] return templates.TemplateResponse( "index.html", { "request": request, "students": students } ) class StudentIn(BaseModel): name: str pwd: str sno: int clas_id: int courses: List[int] = [] @validator("name") def name_must_alpha(cls, value): assert value.isalpha(), 'name must be alpha' return value @validator("sno") def sno_validate(cls, value): assert 1000 < value < 10000, '学号要在2000-10000的范围内' return value # 一对多和多对多的添加 @student_api.post("/") async def addStudent(student_in: StudentIn): # 前端传进参数student_in通过StudentIn检验 # 一对多关系绑定 # 插入到数据库 # 方式1 # student = Student(name=student_in.name, pwd=student_in.pwd, sno=student_in.sno, clas_id=student_in.clas_id) # await student.save() # 数据操作记住await 插入到数据库student表 # 方式2 student = await Student.create(name=student_in.name, pwd=student_in.pwd, sno=student_in.sno, clas_id=student_in.clas_id) # 多对多的关系绑定 student_in.courses是前端传过来的 choose_courses = await Course.filter(id__in=student_in.courses) # 在课程表找到课程,取出课程对象 await涉及数据库操作都要加 await student.courses.add(*choose_courses) # *打散 把每个列表一一传进去 return student # 查询接口 @student_api.get("/{student_id}") async def getOneStudent(student_id: int): student = await Student.get(id=student_id) return student # 编辑接口 @student_api.put("/{student_id}") async def updateStudent(student_id: int, student_in: StudentIn):# 首先查到学生student_id: int,然后在更新到表中student_in: StudentIn data = student_in.dict() # 对象转化为字典 print("data", data) courses = data.pop("courses") # 课程是多对多关系不能被更新需要去掉 await Student.filter(id=student_id).update(**data) # 先查filter 后更新update **打撒 # 设置多对多的选修课 edit_stu = await Student.get(id=student_id) # 取出学生对象 choose_courses = await Course.filter(id__in=courses) # 看课程是否存在找到此课程 await edit_stu.courses.clear() #将之前的关系清掉 await edit_stu.courses.add(*choose_courses) # 更新多对多关系表 return edit_stu @student_api.delete("/{student_id}") async def deleteStudent(student_id: int): deleteCount = await Student.filter(id=student_id).delete() if not deleteCount: raise HTTPException(status_code=404, detail=f"主键为{student_id}的学生不存在") return {}
templates:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous"> </head> <body> <h1>学生信息</h1> <div class="row"> <div class="col-md-8 col-md-offset-2"> <p> <button class="btn primary"><a href="">添加学生</a></button> </p> <table class="table table-bordered table-striped table-hover"> <thead> <tr> <th>学生学号</th> <th>学生姓名</th> <th>学生班级</th> </tr> </thead> <tbody> {% for student in students %} <tr> <td>{{ student.sno }}</td> <td>{{ student.name }}</td> <td>{{ student.clas_id }}</td> </tr> {% endfor %} </tbody> </table> </div> </div> </body> </html>
七、中间件与CORS
7.1、中间件
你可以向 FastAPI 应用添加中间件.
"中间件"是一个函数,它在每个请求被特定的路径操作处理之前,以及在每个响应之后工作.(每个请求都要登录验证)
如果你使用了
yield
关键字依赖, 依赖中的退出代码将在执行中间件后执行.如果有任何后台任务(稍后记录), 它们将在执行中间件后运行.
要创建中间件你可以在函数的顶部使用装饰器 @app.middleware("http")
.
一个中间件(就是个函数)包含两部分一个是请求代码块一个是响应代码块。
中间件参数接收如下参数:
request
.一个函数
call_next
,它将接收request,作为参数.
这个函数将
request
传递给相应的 路径操作.然后它将返回由相应的路径操作生成的
response
.然后你可以在返回
response
前进一步修改它.
import uvicorn from fastapi import FastAPI from fastapi import Request from fastapi.responses import Response import time app = FastAPI() # 注意中间件顺序 # 中间件m2 @app.middleware("http") # 声明一个函数为装饰器 async def m2(request: Request, call_next): # call_next是把请求和响应分开的,在请求执行完不会立即执行响应,去执行下一个中间件请求 # 请求代码块 print("m2 request") response = await call_next(request) # call_next是把请求和响应分开的 # 响应代码块 response.headers["author"] = "yuan" print("m2 response") return response # 中间件m1 @app.middleware("http") # 声明一个函数为装饰器 async def m1(request: Request, call_next): # call_next是把请求和响应分开的,在请求执行完不会立即执行响应,去执行下一个请求 # 请求代码块 print("m1 request") # if request.client.host in ["127.0.0.1", ]: # 黑名单 # return Response(content="visit forbidden") # 如果不符合 直接返回response 不会走下一个中间件的请求 # if request.url.path in ["/user"]: # return Response(content="visit forbidden") start = time.time() response = await call_next(request) # call_next是把请求和响应分开的 # 响应代码块 print("m1 response") end = time.time() response.headers["ProcessTimer"] = str(end - start) return response @app.get("/user") def get_user(): time.sleep(3) print("get_user函数执行") return { "user": "current user" } @app.get("/item/{item_id}") def get_item(item_id: int): time.sleep(2) print("get_item函数执行") return { "item_id": item_id } if __name__ == '__main__': uvicorn.run('main:app', host='127.0.0.1', port=8030, reload=True, debug=True, workers=1)
7.2、CORS
跨域请求和同源请求
模拟跨域
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script> </head> <body> <p>hello world</p> <script> $("p").click(function () { $.ajax({ url: "http://127.0.0.1:8030/user", success: function (res) { console.log(res) console.log(res.user) $("p").html("hello " + res.user) } }) }) </script> </body> </html>
import uvicorn from fastapi import FastAPI from fastapi import Request from fastapi.middleware.cors import CORSMiddleware app = FastAPI() # 自己手写跨域中间件(在所有请求前先执行此处) # @app.middleware("http") # async def CORSMiddleware(request: Request, call_next): # response = await call_next(request) # response.headers["Access-Control-Allow-Origin"] = "*" # return response # faskapi自带的跨域中间件 origins = [ "http://localhost:63342" ] app.add_middleware( CORSMiddleware, allow_origins="*", # *:代表所有客户端 origins代表设置的源 allow_credentials=True, allow_methods=["GET", "POST"], allow_headers=["*"], ) @app.get("/user") def get_user(): print("user:yuan", ) return { "user": "yuan" } if __name__ == '__main__': uvicorn.run('main:app', host='127.0.0.1', port=8030, reload=True, debug=True, workers=1)