Skip to main content

安装

Pywss 依赖 python 3.6+ 版本的部分特性。

如果你刚好使用的是 3.6 及以上版本的话,那么恭喜你,你可以通过pip实现快速安装。

>>> pip3 install pywss
>>> pip3 install pywss -i https://pypi.org/simple
>>> pip3 install pywss -i https://pypi.tuna.tsinghua.edu.cn/simple
依赖说明

Pywss 目前仅依赖一个日志库 loggus,该库是作者对结构化日志库的探索成果,暂时够用😅 后续会考虑切回原生日志库 logging,减少心智负担😅

快速开始

本节以 hello world 为例,从零开始快速搭建一个 web 应用。

准备程序

首先,在本地创建 main.py 文件并写入以下源码。

源码
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()

快速启动

  1. 准备好 python3 环境,并正常安装 pywss 库
  2. 准备好上述程序文件
  3. 在一切准备都完成之后,我们通过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
  1. 我们可以通过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.apppywss.Appapp 对象
ctx.fdsocket.socketsock句柄,用于写操作,一般不直接使用
ctx.rfdsocket.makefilesock句柄,用于读操作,一般不直接使用
ctx.addresstupleHTTP地址信息
ctx.methodstrHTTP请求方法,由 GET/POST/PUT/DELETE/HEAD/PATCH/OPTIONS 组成
ctx.versionstrHTTP协议版本号,参考值:HTTP/1.1
ctx.urlstrHTTP请求URL地址,注意是包含请求参数在内的全量地址。参考值:/api/v1/query?key=value
ctx.url_paramsdictHTTP请求参数,是基于ctx.url解析的结果。举例说明:对于/api/v1/query?key=value地址,ctx.url_params等于{"key": "value"}
ctx.routestrHTTP请求路由,注意是不包含请求参数在内的路由,参考:/api/v1/query
ctx.route_paramsdict用于存储局部匹配下的路径参数,举例说明:对于/api/v1/{name}/{age}路由,ctx.route_params等于{"name": "xx", "age": "xx"}
ctx.headersdictHTTP请求头
ctx.cookiesdictHTTP请求Cookie,是基于ctx.headers["Cookie"]解析的结果
ctx.content_lengthintHTTP请求大小,是基于ctx.headers["Content-Length"]解析的结果
ctx.contentbyteHTTP请求数据体。一般不直接使用,对于需要获取原生请求场景,推荐使用ctx.body()
ctx.datapywss.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()

在这个用例中,logHandlerhelloHandler就是一组关联的Handler,注册一组关联的 Handler 的方式和注册单个 Handler 的方式是一样的, 其调用顺序就是从左至右。

注意! 需要前一个 Handler 执行 ctx.next() 才会触发链式调用进入到下一个 Handler😵

为了方便理解,对于一组关联的 Handler,除了最后一个 Handler 之外,前面的我们都称之为中间件

原理简介

前提说明

本节源代码取自 v0.1.18 版本,最新代码请参考 Github-Pywss

next 源码非常的简洁,核心逻辑仅由几行代码实现:

ctx.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()