Skip to content

Commit e9bbfcd

Browse files
committed
add cn-version
1 parent 23e84ac commit e9bbfcd

File tree

23 files changed

+1070
-32
lines changed

23 files changed

+1070
-32
lines changed

content/archives/_index.fr.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

content/archives/_index.ja.md

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
+++
2+
date = '2025-08-03T10:30:00+08:00'
3+
draft = false
4+
title = 'FastAPI 的工作原理'
5+
+++
6+
FastAPI 的工作原理: 从 routing 到 lifecycle 以及在现实中的使用
7+
8+
### FastAPI
9+
FastAPI 是一个现代的 Python Web 框架, 注重**高性能****开发效率**. 旨在帮助开发者编写结构清晰、可靠的API, 同时尽量减少样板代码 (boilerplate)
10+
11+
其由以下两个库驱动:
12+
- **Starlette**: 负责 Web 服务器逻辑、路由、中间件和异步能力
13+
- **Pydantic**: 基于 Python 类型提示, 处理数据验证、解析和序列化
14+
15+
此外, Fastapi 还有输入验证、基于 Swagger UI 的自动文档生成和代码清晰化的基础
16+
17+
18+
### API 请求周期
19+
Fastapi 的请求生命周期如下
20+
```
21+
客户端请求 (Client Request)
22+
23+
FastAPI App
24+
25+
中间件(Middleware)
26+
27+
路由匹配 (Route Matching)
28+
29+
依赖注入(Dependency Injection)
30+
31+
输入验证 (Input Validation)
32+
33+
端点函数 (Endpoint)
34+
35+
响应序列化 (Response Serialization)
36+
37+
客户端响应 (Client Response)
38+
```
39+
1. 请求首先进入 FastAPI 应用 (本质就是一个 Starlette 应用)
40+
2. 所有中间件优先执行 (如: 日志、错误处理、CORS等)
41+
3. 路由器检查路径和方法, 找到对应的处理函数
42+
4. FastAPI 使用`Depends`解析依赖
43+
5. 使用 Pydantic 自动解析并验证输入数据
44+
6. 执行端点函数, 参数验证完毕
45+
7. 返回结果被序列化为合适的响应格式 (JSON)
46+
8. 响应返回给客户端
47+
48+
### 路由 Router
49+
1. 在应用对象上定义
50+
适合小项目或原型验证
51+
```python
52+
from fastapi import FastAPI
53+
54+
app = FastAPI()
55+
56+
@app.get("/items/{item_id}")
57+
def read_item():
58+
return {"item_id": item_id}
59+
```
60+
2. 使用 APIRouter 模块化
61+
适合大项目
62+
```python
63+
from fastapi import FastAPI
64+
65+
router = APIRouter(prefix="/users", tags=["users"])
66+
67+
@router.get("/{user_id}")
68+
def get_user(user_id: int):
69+
return {"user_id": user_id}
70+
```
71+
使用`APIRouter`可以将相关的端点分组, 添加前缀和标签, 保持代码结构清晰模块化
72+
73+
当某个请求与端点匹配时, FastAPI 内部执行一下步骤:
74+
1. Starlette 找到对应路由, 并创建一个`APIRouter`实例
75+
2. FastAPI 使用`get_router_header()`包装端点函数并解析依赖
76+
3. 使用 Pydantic 或基本类型对请求数据解析与验证
77+
4. 装饰函数被调用, 传入验证后的参数
78+
5. 返回值被序列化为响应对象
79+
80+
### 依赖注入: 干净、可复用的逻辑
81+
FastAPI 有一个轻量且强大的依赖注入系统, 可以进行数据库链接、身份验证信息或配置信息等
82+
```python
83+
from fastapi import Depends
84+
85+
def get_db():
86+
db = create_db_session()
87+
try:
88+
yield db
89+
finally:
90+
db.close()
91+
92+
@app.get("/items/")
93+
def read_items(db=Depends(get_db)):
94+
return db.query(item).all()
95+
```
96+
使用`Depends`, FastAPI 会负责调用`get_db`, 处理生成器生命周期, 并将结果注入到函数中
97+
98+
99+
### 原生支持异步 (Async)
100+
不同于一些后加入 async 的框架, FastAPI 一开始就设计为支持 async/await
101+
```python
102+
from fastapi import FastAPI
103+
import asyncio
104+
105+
app = FastAPI()
106+
107+
@app.get("/hi")
108+
async def greet():
109+
await asyncio.sleep(1)
110+
return "Hello? World?"
111+
```
112+
当 fastapi 收到 `/hi` 这个 URL 的 GET 请求时,会自动调用 async greet(), 无需在任何地方添加 await
113+
114+
但是, 对于其他的 async def 函数, 调用的时候必须在前面加上 await
115+
116+
> FastAPI 会运行一个异步事件循环,用于执行异步路径函数(async path functions),同时也会使用一个线程池来处理同步函数(synchronous path functions), 这样就不需要手动调用 `asyncio.gather()``asyncio.run()` 之类的方法
117+
118+
119+
### 示例: CURD API
120+
```python
121+
from fastapi import FastAPI
122+
from pydantic import BaseModel
123+
124+
app = FastAPI()
125+
126+
class Item(BaseModel):
127+
name: str
128+
description: str = None
129+
price: float
130+
tax: float = None
131+
132+
@app.post("/items/")
133+
async def create_item(item: Item):
134+
total = item.price + (item.tax or 0)
135+
return {"name": item.name, "total_price": total}
136+
137+
@app.get("/")
138+
def read_root():
139+
return {"message": "FastAPI is working!"}
140+
```
141+
142+
运行
143+
```
144+
uvicorn main:app --reload
145+
```
146+
147+
- 还可以使用 Gunicorn 部署4个 Uvicorn 异步服务
148+
```
149+
gunicorn main:app --workers 4 --worker-class \
150+
uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
151+
```
152+
实际上也可以直接诶使用 uvicorn 运行多个进程, 但是这样无法进行进程管理,因此使用 gunicorn 的方法一般更多被使用
153+
154+
### 性能提升
155+
如果 API 返回大量数据, 使用 ORJSON 加快序列化速度
156+
```python
157+
from fastapi import FastAPI
158+
from fastapi.responses import ORJSONResponse
159+
160+
app = FastAPI(default_response_class=ORJSONResponse)
161+
```
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
+++
2+
date = '2025-08-02T10:30:00+08:00'
3+
draft = true
4+
title = '微服务 API'
5+
+++
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
date: 2025-08-04T10:30:00+08:00
3+
draft: false
4+
title: "通过注释执行任意 Python 代码"
5+
---
6+
通过注释执行任意Python代码
7+
8+
### 问题描述
9+
Q: 只能控制一行的.py代码中注释的内容(\n\r均会被替换为空字符), 如何执行任意代码?
10+
A: 在注释#中, 构造一个.zip 文件, python 会将该内容当成一个zip包执行, 触发任意代码执行
11+
12+
### 解决方案
13+
- 从 Python 3.5 起, 可以直接执行一个 .zip 文件
14+
```python3
15+
python myapp.zip
16+
```
17+
前提是ZIP 包中包含一个顶层的`__main__.py`文件, Python 会把它当作 zipapp, 自动解压并运行`__main__.py`
18+
19+
> Python 会从末尾找到 ZIP 的目录结构, 而不是依赖文件头, 所以前面的“垃圾”字节会被忽略
20+
21+
Python 源码中的任何行, 只要以 # 开头, 解释器都会忽略后面内容, 因此可以:
22+
-ZIP 文件的数据藏在 Python 源码中的注释中(开头加 #
23+
-ZIP 数据直接拼接在 Python 文件的后面, 只保证文件头部分是合法 Python
24+
- ZIP 不关心前缀, Python 只要最前面是有效源码, 也不会管后面
25+
26+
### 难点
27+
ZIP 文件头包含**二进制字段**,比如
28+
- 偏移量(文件数据相对于 ZIP 开头的位置)
29+
- 长度(文件名长度、注释长度等)
30+
- 这些值写死在 header 里, 是十六进制整数
31+
- 如果这些字节中出现了像 \x00、\xFF 等非 ASCII 内容, Python 就不能把它当注释
32+
33+
解决方法: 暴力穷举合法组合
34+
35+
想办法 **调整偏移值和结构位置**,使得最终写出来的 ZIP 文件
36+
- 所有的字段值都转化为 可打印字符(ASCII 范围内)
37+
- 所有 binary 字段看起来都像合法的注释字符串
38+
于是用 `itertools.product(range(256), repeat=2)` 暴力尝试偏移组合,只要碰巧生成的 ZIP 包所有关键字节都在可打印范围内(ASCII 32~126),就认为成功。
39+
40+
41+
42+
下面是`generate_polygloy_zip.py`代码, 会生成一个符合要求的`polygloy.py`代码, 最后运行该代码, 可以执行Body里面的内容`BODY = b"print('FROM MAIN.py FILE!!!')#"`
43+
44+
```python3
45+
# struct: 按字节结构打包数据,方便构造 ZIP 文件二进制头
46+
# itertools: 用来暴力枚举 CRC 校验和后缀(确保安全ASCII)
47+
# zlib: 计算 CRC32 校验和
48+
import struct, itertools, zlib
49+
50+
# 文件开头代码
51+
# encode(): Unicode 字符串 -> bytes 字节串
52+
JUNK_HEAD = """print("Hello World!")
53+
# This is a comment. Here's another:
54+
# """.encode()
55+
56+
# 文件结尾代码
57+
JUNK_TAIL = """
58+
print("Thanks for playing!")"""
59+
60+
61+
# zip 文件核心代码
62+
# b: 字节串
63+
FILENAME = b"__main__.py"
64+
BODY = b"print('FROM MAIN.py FILE!!!')#"
65+
66+
67+
# 校验 CRC 是否为 ASCII-safe
68+
def ascii_safe(x: int) -> bool:
69+
return all(((x >> (8*i)) & 0x80) == 0 for i in range(4))
70+
71+
72+
# 检查 32 位整数的四个字节,每个字节最高位(0x80)是否为 0,即是否为 ASCII 范围内的字节
73+
def find_suffix(core: bytes, length: int = 4) -> tuple[bytes, int]:
74+
"""
75+
- ZIP 文件 CRC32 计算结果必须 ASCII-safe(低于 0x80)
76+
- 这里用暴力方法,给 payload 后面加4字节后缀,找到合适的后缀让 CRC32 满足 ASCII-safe 条件
77+
"""
78+
printable = range(0x20, 0x7f)
79+
for tail in itertools.product(printable, repeat=length):
80+
payload = core + bytes(tail)
81+
crc = zlib.crc32(payload) & 0xFFFFFFFF
82+
if ascii_safe(crc):
83+
return bytes(tail), crc
84+
85+
raise RuntimeError("No ASCII-safe CRC found.")
86+
87+
# 计算最终 payload
88+
SUFFIX, CRC = find_suffix(BODY)
89+
PAYLOAD = BODY + SUFFIX
90+
SIZE = len(PAYLOAD)
91+
92+
def le32(x): return struct.pack("<I", x) # 4字节小端无符号整数
93+
def le16(x): return struct.pack("<H", x) # 2字节小端无符号整数
94+
95+
# ZIP 结构中各签名常量
96+
SIG_LFH = 0x04034B50 # 本地文件头 Local File Header
97+
SIG_CDH = 0x02014B50 # 中央目录头 Central Directory Header
98+
SIG_EOCD = 0x06054B50 # 结束目录头 End of Central Directory
99+
100+
# zip 文件偏移量设置
101+
delta = len(JUNK_HEAD)
102+
103+
# 构建 Local File Header
104+
"""
105+
Local File Header 是 ZIP 格式中的一部分,告诉解压程序该文件的元信息
106+
- version needed to extract,flags,compression method 等字段置 0 表示无压缩,简单存储
107+
- CRC32、压缩大小、解压大小都是我们计算的
108+
- 文件名长度和文件名
109+
"""
110+
lfh = (
111+
le32(SIG_LFH) +
112+
le16(0) + le16(0) + le16(0) + le16(0) + le16(0) +
113+
le32(CRC) + le32(SIZE) + le32(SIZE) +
114+
le16(len(FILENAME)) + le16(0) +
115+
FILENAME
116+
)
117+
118+
# 构建 Central Directory Header
119+
"""
120+
- Central Directory 是 ZIP 文件目录结构,记录每个文件信息和偏移,
121+
- 其中重要的是 relative offset of LFH,也就是 Local File Header 在整个 ZIP 文件里的偏移,必须加上 delta
122+
"""
123+
cdh = (
124+
le32(SIG_CDH) +
125+
le16(0) + le16(0) + le16(0) + le16(0) + le16(0) + le16(0) +
126+
le32(CRC) + le32(SIZE) + le32(SIZE) +
127+
le16(len(FILENAME)) + le16(0) + le16(0) +
128+
le16(0) + le16(0) + le32(0) + le32(delta) +
129+
FILENAME
130+
)
131+
132+
# 确保偏移量 ASCII-safe
133+
"""
134+
- ZIP 目录偏移需要是 ASCII 字节,否则写入 .py 文件时会出错
135+
- 这里通过填充若干 \x00 字节,保证偏移合法
136+
"""
137+
cd_offset = delta + len(lfh) + len(PAYLOAD)
138+
pad = 0
139+
while not ascii_safe(cd_offset + pad):
140+
pad += 1
141+
padding = b'\x00' * pad
142+
cd_offset += pad
143+
144+
# 构建 End of Central Directory Header
145+
"""
146+
EOCD 记录 ZIP 中央目录大小、偏移及注释长度等信息
147+
"""
148+
eocd = (
149+
le32(SIG_EOCD) +
150+
le16(0) + le16(0) +
151+
le16(1) + le16(1) +
152+
le32(len(cdh)) +
153+
le32(cd_offset) +
154+
le16(len(JUNK_TAIL))
155+
)
156+
157+
# 拼接完整 ZIP 内容
158+
zip_bytes = lfh + PAYLOAD + padding + cdh + eocd
159+
zip_bytes = bytearray(zip_bytes)
160+
assert all(b < 0x80 for b in zip_bytes), "非 ASCII 字节存在"
161+
162+
# 写入 polyglot.py 文件
163+
with open("polyglot.py", "wb") as f:
164+
f.write(JUNK_HEAD + zip_bytes + JUNK_TAIL.encode())
165+
166+
# 运行提示
167+
print("✅ polyglot.py 生成完毕。运行它即可执行嵌入的 __main__.py 内容:")
168+
print(" $ python3 polyglot.py")
169+
```
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
+++
22
date = '2025-08-01T10:30:00+08:00'
33
draft = true
4-
title = 'Microservice APIs'
4+
title = 'Python Project Structures'
55
+++
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
+++
2+
date = '2025-08-01T10:30:00+08:00'
3+
draft = true
4+
title = 'Python 项目结构'
5+
+++

public/archives/index.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Archives on Starslayerx' Blog</title><link>https://starslayerx.github.io/archives/</link><description>Recent content in Archives on Starslayerx' Blog</description><generator>Hugo</generator><language>en-US</language><atom:link href="https://starslayerx.github.io/archives/index.xml" rel="self" type="application/rss+xml"/><item><title>Archives</title><link>https://starslayerx.github.io/archives/_index.fr/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://starslayerx.github.io/archives/_index.fr/</guid><description/></item><item><title>アーカイブ</title><link>https://starslayerx.github.io/archives/_index.ja/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://starslayerx.github.io/archives/_index.ja/</guid><description/></item></channel></rss>
1+
<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Archives on Starslayerx' Blog</title><link>https://starslayerx.github.io/archives/</link><description>Recent content in Archives on Starslayerx' Blog</description><generator>Hugo</generator><language>en-US</language><atom:link href="https://starslayerx.github.io/archives/index.xml" rel="self" type="application/rss+xml"/></channel></rss>

public/en/sitemap.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?xml version="1.0" encoding="utf-8" standalone="yes"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"><url><loc>https://starslayerx.github.io/posts/executing-arbitrary-python-code-from-a-comment/</loc><lastmod>2025-08-04T10:30:00+08:00</lastmod></url><url><loc>https://starslayerx.github.io/posts/</loc><lastmod>2025-08-04T10:30:00+08:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/posts/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/posts/"/></url><url><loc>https://starslayerx.github.io/posts/how-fastapi-works/</loc><lastmod>2025-08-03T10:30:00+08:00</lastmod></url><url><loc>https://starslayerx.github.io/posts/blaugust/</loc><lastmod>2025-08-01T10:00:00+08:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/posts/blaugust/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/posts/blaugust/"/></url><url><loc>https://starslayerx.github.io/</loc><lastmod>2023-01-01T08:00:00-07:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/"/></url><url><loc>https://starslayerx.github.io/archives/_index.fr/</loc></url><url><loc>https://starslayerx.github.io/archives/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/archives/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/archives/"/></url><url><loc>https://starslayerx.github.io/categories/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/categories/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/categories/"/></url><url><loc>https://starslayerx.github.io/tags/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/tags/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/tags/"/></url><url><loc>https://starslayerx.github.io/archives/_index.ja/</loc></url></urlset>
1+
<?xml version="1.0" encoding="utf-8" standalone="yes"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"><url><loc>https://starslayerx.github.io/posts/executing-arbitrary-python-code-from-a-comment/</loc><lastmod>2025-08-04T10:30:00+08:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/posts/%E9%80%9A%E8%BF%87%E6%B3%A8%E9%87%8A%E6%89%A7%E8%A1%8C%E4%BB%BB%E6%84%8F-python-%E4%BB%A3%E7%A0%81/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/posts/executing-arbitrary-python-code-from-a-comment/"/></url><url><loc>https://starslayerx.github.io/posts/</loc><lastmod>2025-08-04T10:30:00+08:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/posts/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/posts/"/></url><url><loc>https://starslayerx.github.io/posts/how-fastapi-works/</loc><lastmod>2025-08-03T10:30:00+08:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/posts/fastapi-%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/posts/how-fastapi-works/"/></url><url><loc>https://starslayerx.github.io/posts/blaugust/</loc><lastmod>2025-08-01T10:00:00+08:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/posts/blaugust/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/posts/blaugust/"/></url><url><loc>https://starslayerx.github.io/</loc><lastmod>2023-01-01T08:00:00-07:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/"/></url><url><loc>https://starslayerx.github.io/archives/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/archives/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/archives/"/></url><url><loc>https://starslayerx.github.io/categories/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/categories/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/categories/"/></url><url><loc>https://starslayerx.github.io/tags/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/tags/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/tags/"/></url></urlset>

public/index.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ For develpoers, blogging isn&amp;rsquo;t just sharing, it&amp;rsquo;s about orga
1616
&lt;li>Architecture notes: async patterns, micorservice, data flow&lt;/li>
1717
&lt;li>Learning notes &amp;amp; translation of blogs: deep dive into code and quality tech atricles&lt;/li>
1818
&lt;/ul>
19-
&lt;p>I&amp;rsquo;m not necessarily writing one post per day - some days I might write multiple posts in advance - but the goal is to publish daily with a focus on consistency, reusability, and value.&lt;/p></description></item><item><title>Archives</title><link>https://starslayerx.github.io/archives/_index.fr/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://starslayerx.github.io/archives/_index.fr/</guid><description/></item><item><title>アーカイブ</title><link>https://starslayerx.github.io/archives/_index.ja/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://starslayerx.github.io/archives/_index.ja/</guid><description/></item></channel></rss>
19+
&lt;p>I&amp;rsquo;m not necessarily writing one post per day - some days I might write multiple posts in advance - but the goal is to publish daily with a focus on consistency, reusability, and value.&lt;/p></description></item></channel></rss>

0 commit comments

Comments
 (0)