安装
Pywss 依赖 python 3.6+ 版本的部分特性。
如果你刚好使用的是 3.6 及以上版本的话,那么恭喜你,你可以通过pip
实现快速安装。
- pip安装
- 源码安装
>>> pip3 install pywss
>>> pip3 install pywss -i https://pypi.org/simple
>>> pip3 install pywss -i https://pypi.tuna.tsinghua.edu.cn/simple
>>> git clone https://github.com/czasg/pywss.git
>>> cd pywss
>>> python3 setup.py install
Pywss 目前仅依赖一个日志库 loggus,该库是作者对结构化日志库的探索成果,暂时够用😅 后续会考虑切回原生日志库 logging,减少心智负担😅
快速开始
本节以 hello world 为例,从零开始快速搭建一个 web 应用。
准备程序
首先,在本地创建 main.py 文件并写入以下源码。
- main.py
- GET/hi
- POST/hello
import pywss
def helloHandler(ctx: pywss.Context): # 处理函数仅 pywss.Context 一个参数哦~
ctx.write({"hello": "world"})
def main():
app = pywss.App()
app.get("/hi", lambda ctx: ctx.write("hi~")) # 匿名函数也是不错的选择~
app.post("/hello", helloHandler)
app.run(port=8080) # 默认端口为8080
if __name__ == '__main__':
"""
python3 main.py 启动服务
"""
main()
>>> curl localhost:8080/hi
hi~
>>> curl -X POST localhost:8080/hello
{"hello": "world"}
快速启动
- 准备好 python3 环境,并正常安装 pywss 库
- 准备好上述程序文件
- 在一切准备都完成之后,我们通过
python3 main.py
快速启动服务。当看到类似日志时,则表示服务已经正常启动
time="2024-01-18 15:54:32.497093" level=info msg="bind route" type=fullmatch route="GET:/hi" handlers="['<lambda>']"
time="2024-01-18 15:54:32.497093" level=info msg="bind route" type=fullmatch route="POST:/hello" handlers="['helloHandler']"
time="2024-01-18 15:54:32.513093" level=info msg="server start" version="0.1.27" host="0.0.0.0" port=8080
- 我们可以通过
curl
快速请求服务
>>> curl localhost:8080/hi
hi~
>>> curl -X POST localhost:8080/hello
{"hello": "world"}
至此,一个简单的 hello world 应用就已经完成了😏
程序说明
在 main.py
中我们可以看到:
1.首先是初始化模块
- 在第7行初始化了一个
app
。 - 在第10行执行
app.run
启动 web 应用。
这两行代码,囊括了 app 从 初始化 -> 启动 的整个周期。
2.其次是路由注册模块
- 第8行
app.get()
表示 注册 Http Get 方法,其参数分别表示:- 绑定路由
/hi
- 绑定业务处理逻辑
lambda ctx: ctx.write("hi~")
- 绑定路由
- 第9行
app.post()
表示 注册 Http Post 方法,其参数分别表示:- 绑定路由
/hello
- 绑定业务处理逻辑
helloHandler
- 绑定路由
什么是Context
在 Pywss 中,pywss.Context
(后文均使用 ctx 代替) 贯穿于单次请求的整个生命周期,是 Pywss 用于管理请求的上下文对象。
对于 Pywss 的 Handler(业务逻辑处理模块) 来说,仅且支持 ctx 这一个参数。
所以,ctx 是一个集 HTTP 请求报文解析、HTTP 响应报文构建、信息传递 于一体的上下文对象。
其主要属性有:
属性 | 类型 | 说明 |
---|---|---|
ctx.app | pywss.App | 根 app 对象 |
ctx.fd | socket.socket | sock句柄,用于写操作,一般不直接使用 |
ctx.rfd | socket.makefile | sock句柄,用于读操作,一般不直接使用 |
ctx.address | tuple | HTTP地址信息 |
ctx.method | str | HTTP请求方法,由 GET/POST/PUT/DELETE/HEAD/PATCH/OPTIONS 组成 |
ctx.version | str | HTTP协议版本号,参考值:HTTP/1.1 |
ctx.url | str | HTTP请求URL地址,注意是包含请求参数在内的全量地址。参考值:/api/v1/query?key=value |
ctx.url_params | dict | HTTP请求参数,是基于ctx.url 解析的结果。举例说明:对于/api/v1/query?key=value 地址,ctx.url_params 等于{"key": "value"} |
ctx.route | str | HTTP请求路由,注意是不包含请求参数在内的路由,参考:/api/v1/query |
ctx.route_params | dict | 用于存储局部匹配下的路径参数,举例说明:对于/api/v1/{name}/{age} 路由,ctx.route_params 等于{"name": "xx", "age": "xx"} |
ctx.headers | dict | HTTP请求头 |
ctx.cookies | dict | HTTP请求Cookie,是基于ctx.headers["Cookie"] 解析的结果 |
ctx.content_length | int | HTTP请求大小,是基于ctx.headers["Content-Length"] 解析的结果 |
ctx.content | byte | HTTP请求数据体。一般不直接使用,对于需要获取原生请求场景,推荐使用ctx.body() |
ctx.data | pywss.Data | 基于字典实现,用于Context上下文信息传递。使用方式参考 ctx.data.key = value |
ctx.body() | func | 解析请求,以 BYTE 形式返回 |
ctx.json() | func | 解析请求,以 JSON 形式返回 |
ctx.form() | func | 解析请求,以 FORM 形式返回 |
ctx.file() | func | 解析请求,以 Dict[str, pywss.File] 形式返回 |
ctx.stream() | func | 流式读取请求,用于某些大数据场景 |
ctx.set_status_code() | func | 指定HTTP响应报文状态码 |
ctx.set_header() | func | 指定HTTP响应头 |
ctx.set_content_type() | func | 指定HTTP响应类型 |
ctx.set_cookie() | func | 指定HTTP响应Cookie |
ctx.write() | func | 指定HTTP响应体,支持 String、JSON、Chunked 等,详情见响应模块 |
ctx.flush() | func | 发送HTTP响应报文,一般不需要自己调用。flush 仅且只能调用一次,多次调用仅第一次生效~ |
什么是Next
Next 本质是一个链式调用。
Pywss 允许路由关联一组 Handler,并通过 next 实现链式调用。下面我们通过一个日志中间件用例来快速上手 next 机制。
import pywss
import time
import random
def logHandler(ctx: pywss.Context):
startTime = time.time()
ctx.next()
cost = time.time() - startTime
print(f"{ctx.method} - {ctx.route} - cost: {cost: .2f}")
def helloHandler(ctx: pywss.Context):
ctx.write({"hello": "world"})
app = pywss.App()
app.post("/hello", logHandler, helloHandler)
app.run()
在这个用例中,logHandler
和helloHandler
就是一组关联的Handler,注册一组关联的 Handler 的方式和注册单个 Handler 的方式是一样的,
其调用顺序就是从左至右。
为了方便理解,对于一组关联的 Handler,除了最后一个 Handler 之外,前面的我们都称之为中间件。
原理简介
本节源代码取自 v0.1.18 版本,最新代码请参考 Github-Pywss
next
源码非常的简洁,核心逻辑仅由几行代码实现:
def next(self) -> None:
if self._handler_index >= len(self._handlers):
return
index = self._handler_index
self._handler_index += 1
self._handlers[index](self)
具体实现流程此处不展开,感兴趣的同学可以直接阅读源码。
使用方法
在 pywss 中,提供了两种方式来注册中间件:
- app use 全局注册 ,针对全部路由生效
- route bind 局部注册 ,针对指定路由生效
下面通过一个logHandler
来实战演示下如何通过next
快速实现一个日志中间件~
import pywss
import time
import random
def logHandler(ctx: pywss.Context):
startTime = time.time()
ctx.next()
cost = time.time() - startTime
print(f"{ctx.method} - {ctx.route} - cost: {cost: .2f}")
app = pywss.App()
app.use(logHandler)
app.get("/hi", lambda ctx: time.sleep(random.randint(1, 3)) or ctx.write("Hi~"))
app.post("/hello", lambda ctx: time.sleep(random.randint(1, 3)) or ctx.write("HelloWorld"))
app.run()
import pywss
import time
import random
def logHandler(ctx: pywss.Context):
startTime = time.time()
ctx.next()
cost = time.time() - startTime
print(f"{ctx.method} - {ctx.route} - cost: {cost: .2f}")
app = pywss.App()
app.get("/hi", lambda ctx: time.sleep(random.randint(1, 3)) or ctx.write("Hi~"))
app.post("/hello", logHandler, lambda ctx: time.sleep(random.randint(1, 3)) or ctx.write("HelloWorld"))
app.run()