请求-响应过程
在Robyn高性能Web框架系列01:Robyn快速入门 一节中,我们介绍了Robyn在设计理念和代码风格上借鉴了FastAPI,在使用上基本可以照搬FastAPI,甚至可以将原来基于FastAPI编写的程序直接转换为Robyn程序,因此对于Robyn的基本用法我们将快速通过,小伙伴们可以参考本号的“FastAPI系列”教程。
Robyn请求
在Robyn中,来自客户的端的HTTP请求信息经过解析后放在了Request 对象中,我们可以在路由函数中通过request参数来访问Request 对象,请详细成员信息如下:
- url(Url): 请求的URL地址
- ip_addr (Optional[str]): 客户端的 IP 地址
- method(str):请求的HTTP方法。
e.g. GET, POST, PUT, DELETE
- headers(dict[str, str]):请求的标头。
e.g. {"Content-Type": "application/json"}
- body (Union[str, bytes]):请求的主体。如果请求是 JSON,则它将是一个字典。
- params (dict[str, str]): 请求的参数。
e.g. /user/:id -> {"id": "123"}
- query_params(QueryParams):请求的查询参数。
e.g. /user?id=123 -> {"id": [ "123" ]}
- form_data(dict):请求的表单数据。
- files(dict):请求的文件数据。
- 身份(Identity):客户端的身份标签
1、请求的URL、来源IP、HTTP方法
在Request 对象中可以通过request.url、request.ip_addr、request.method的属性获得HTTP请求的基本信息。
from robyn import Robyn, Response
from robyn.robyn import QueryParams
from robyn.types import Method, IPAddress, PathParams, FormData, Files
@app.get("/info")
async def info(request):
return {
"RequestURL": request.url.path,
"RequestIP": request.ip_addr,
"RequestMethod": request.method
}
2、HTTP headers、cookies
在Request 对象中提供了headers集合,可以通过该集合获得相应的header数据。Robyn没有独立的cookie集合,只能通过headers集合获得cookie值,自行解析。
@app.get("/headers")
async def headers(request):
return {
"host": request.headers.get("host"),
"user-agent": request.headers.get("user-agent"),
"cookie": request.headers.get("cookie")
}
3、路径参数、查询参数
Request 对象会根据路由配置进一步解析请求url的信息,提取出路径参数和查询参数分别放置在path_params和query_params中。
@app.get("/params/:foo")
async def params(request):
foo = request.path_params.get('foo', 'default_foo')
msg = request.query_params.get('msg', 'default message')
return {'msg': 'Request params received'}
但是在Robyn中,是无法直接通过参数名 获取路径参数或查询参数的,下列代码不成立:
@app.get("/params/:foo")
async def params(foo: str):
return {'foo': foo}
Robyn 支持使用*extra
路由定义中的语法捕获额外的路径参数。这允许你捕获已定义路由之后的 URL 路径中的任何其他段。
@app.get("/params/extra/*extra")
async def param_extra(request: Request):
extra = request.path_params["extra"]
return extra
4、请求体、表单与文件
在Request 对象中提供了body属性用以获取请求体数据,如果请求是 JSON,它会被解析成一个字典。Request 对象也提供了form_data和files两个集合,用以访问客户端提交的表单和文件数据。
@app.get("/body")
async def body(request):
body = request.body
form = request.form_data
files = request.files
file_names = list(files.keys())
return {'msg': 'Request body received'}
5、请求参数的对象化
除了可以通过Request 对象访问请求数据外,Robyn还允许通过注入到路由函数的类型化对象访问这些数据,如:
@app.get("/obj_param/:foo")
async def obj_param(req_method:Method, req_ip:IPAddress,path:PathParams,query:QueryParams,form:FormData,files:Files):
path_foo = path.get('foo')
query_foo = query.get('foo', 'default_query_foo')
form_foo = form.get('foo')
files_foo = files.get('foo')
print(path_foo)
print(query_foo)
print(form_foo)
print(files_foo)
return {
"RequestIP": req_ip,
"RequestMethod": req_method
}
总体而言,在请求参数的处理上,Robyn还是不如FastAPI那么丝滑、简洁。
Robyn路由
1、路由规则
Robyn的路由规则比较简单,简单的勉强够用,它主要包括以下路由规则 :
- 静态路径,如 /ping
- 动态参数,如 :item_id
- 通配符路径,如 /*extra
2、路由注册
Robyn提供了通过注解的方式注册路由:
from robyn import Robyn
app = Robyn(__file__)
@app.get("/say_hello")
def say_hello():
return "Hello world!"
也可以通过add_route
方法注册路由:
from robyn import Robyn
app = Robyn(__file__)
def say_hello():
return "Hello world!"
app.add_route(
route_type=HttpMethod.GET,
endpoint="/say_hello",
handler = say_hello
)
针对较大的项目, Robyn还提供了SubRouter
用于模块化路由管理:
from robyn import Robyn, SubRouter
app = Robyn(__file__)
# 声明user子路由
user_router = SubRouter(__name__, prefix="/user")
@user_router.get("/info")
async def info():
return {
"Name": "Robyn",
"Age": 20
}
# 注册user子路由
app.include_router(user_router)
Robyn也能为不同的HTTP请求方法如 GET、POST、PUT、PATCH 和 DELETE绑定相同名称的路由函数:
@app.get("/say_hello")
async def say_hello():
return {"message": "Hello From Get!"}
@app.post("/say_hello")
async def say_hello():
return {"message": "Hello From Post!"}
3、同步、异步函数
像FastAPI 一样,Robyn的路由函数也可以是同步函数或异步函数,只不过Robyn的异步函数性能上有着比较的明显提升。
Robyn响应
Robyn同样也提供了一个Response类,用于设置返回客户端的控制状态码、响应头、内容格式、Cookies 和二进制流输出等HTTP响应信息。
- status_code:HTTP 状态码,比如 200、404。
- headers:响应头集合,键值对形式。
- description:简单双向输出内容,用于纯文本或字节响应。
- body:用于二进制或生成器内容,适用于流式输出。
- set_cookie(…):用于设置 Set-Cookie 的方法。
1、HTTP status、 headers、cookies
以下为基本的Response使用代码:
@app.get("/basic_resp")
async def basic_resp(request:Request):
resp = Response(
status_code=202,
headers={"X-Note": "Accepted"},
description="Send a basic response",
)
# 设置 Cookie
resp.set_cookie("session", "abc123")
return resp
以下为向客户端输出二进制文件流的Response代码:
@app.get("/stream")
async def stream(request):
# 流式输出二进制内容
return Response(
status_code=200,
headers={"Content-Type": "application/octet-stream"},
body=(f"line {i}\n".encode() for i in range(10))
)
以下为输出重定向(Redirect)的Response代码:
@app.get("/redirect")
async def index():
return Response(status_code=307, headers={"Location": "/login"})
2、响应主体
在Robyn中没有类似于FastAPI的JSONResponse、HTMLResponse、PlainTextResponse、FileResponse、StreamingResponse、RedirectResponse等Response派生类型,但它会自动根据路由函数的返回类型判断响应类型,具体规则如下:
- dict → JSONResponse
- str / bytes → PlainTextResponse
- 返回 HTML 字符串 → HTMLResponse
- 返回 Response(body=…) 生成器或字节流 → StreamResponse
返回一个PlainText响应体
@app.get("/plain")
def plain_resp(request):
return Response(
status_code=200,
headers={"X-Mode": "plain"},
description="Plain text",
)
返回一个JSON响应体
@app.get("/json")
async def json_resp():
return {"msg": "hello", "ok": True}
返回一个Stream响应体
@app.get("/stream")
async def stream_resp():
return Response(
body=(b"num:%d\n" % i for i in range(5)),
headers={"Content-Type": "text/plain"},
)
返回一个HTML响应体
@app.get("/html")
async def html_resp():
return "<h1>Hello HTML</h1>"
3、全局header
Robyn中还提供了一种为整个应用输出统一header的方式,从一定程度上减少了路由函数的重复劳动:
app.add_response_header("content-type", "application/json")
app.set_response_header("content-type", "application/json")
app.exclude_response_headers_for(["/login", "/signup"])
Robyn在一个Web框架最为常用的“HTTP请求-响应过程”的实现上仅仅做到了可用的水平上,比起FastAPI这样的成熟框架还是有不少的优化空间的。