本节摘要:Web 框架的一切都建立在同一个最小模型上——"请求进来、路由分发、处理函数、响应出去"。本节先手工还原这个模型,再对比 Flask(WSGI 同步一代)与 FastAPI(ASGI 异步一代)的设计差异,讲清装饰器路由、依赖注入与数据校验如何复用前八章机制,最后给出部署层的基本认识。
剥掉所有框架特性,HTTP 服务就是一段分发循环:
from http.server import BaseHTTPRequestHandler, HTTPServer ROUTES = {} def route(path): # 眼熟吗?——7.2 节的注册式装饰器 def deco(fn): ROUTES[path] = fn return fn return deco @route("/hello") def hello(params): return "text/plain", "你好,Web" class Handler(BaseHTTPRequestHandler): def do_GET(self): fn = ROUTES.get(self.path) if fn is None: self.send_error(404); return ctype, body = fn(None) self.send_response(200) self.send_header("Content-Type", ctype) self.end_headers() self.wfile.write(body.encode("utf-8")) HTTPServer(("", 8000), Handler).serve_forever()
三十行代码里有路由表(dict,2.2 节)、注册装饰器(7.2 节)、字符串编码(2.3 节)。Flask 和 FastAPI 本质上是把这具骨架工业化:加上 URL 变量提取、中间件、错误处理、序列化。理解骨架,框架就只是"有服务商的版本"。
Flask 的最小应用与它的心智模型(WSGI:一个请求一个同步调用):
from flask import Flask, jsonify, request app = Flask(__name__) @app.get("/orders/<int:oid>") # 装饰器注册路由 + 类型化路径参数 def get_order(oid): order = db_lookup(oid) # 同步 IO:等数据库时占着线程 if order is None: return jsonify(error="not found"), 404 return jsonify(order) @app.post("/orders") def create_order(): data = request.get_json() ... # 手工校验数据——Flask 不管这件事 return jsonify(id=new_id), 201
Flask 的哲学是"微内核 + 自由拼装":核心只管路由与请求上下文,表单、数据库、认证全凭自选扩展。灵活的代价是工程约束弱——项目大了,路由结构、校验方式、分层习惯全看团队自律。
FastAPI 建立在 ASGI 与类型系统上,第 7 章与 8.2 节的知识在它身上全数兑现:
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class OrderIn(BaseModel): # 7.3 节的 pydantic 模型 sku: str qty: int = 1 @app.post("/orders", status_code=201) async def create_order(order: OrderIn): # 参数即模型:自动校验 + 文档 oid = await db_save(order) # await 型 IO:等待时让出(8.2 节) return {"id": oid, "total": order.qty} @app.get("/orders/{oid}") async def read_order(oid: int): # 路径参数按注解转换,非法值 422 ...
FastAPI 的三个"魔法"逐一对应回去:路由装饰器是 7.2 节注册模式;请求校验是 7.3 节 pydantic 在信任边界的落地(错误请求自动得到 422 与字段级报告);异步端点是 8.2 节事件循环的服务化——IO 等待时让出线程,单进程扛海量并发连接。它还免费附赠 OpenAPI 交互文档(/docs),因为类型注解本身就是接口描述。
两代框架的选型不必纠结,一张表说清:
| 维度 | Flask | FastAPI |
|---|---|---|
| 接口协议 | WSGI,同步 | ASGI,同步异步皆可 |
| IO 密集高并发 | 靠多进程/多线程硬扛 | 事件循环,单进程扛海量连接 |
| 数据校验 | 自选扩展,手工 | pydantic 内建,注解即契约 |
| API 文档 | 扩展生成 | 内建 OpenAPI |
| 生态成熟度 | 极其成熟,老项目遍地 | 新项目主流,生态快速生长 |
经验法则:纯 API 服务、尤其 IO 重的(查库、调下游、推送),新项目默认 FastAPI;维护存量 Flask、或团队对同步模型更熟,Flask 依旧可靠。混合形态(FastAPI 里跑阻塞库)用 8.2 节的 to_thread 桥接。
开发服务器的用法(uvicorn main:app --reload)只属于开发。生产部署记住三件事:其一,前面放反向代理(Nginx 一类)管 TLS、静态文件与限流;其二,应用层用 ASGI 服务器(uvicorn/gunicorn 多 worker)多进程跑满核心——8.1 节的进程模型在部署层的直接应用;其三,配置与密钥走环境变量,代码库里只读不存。Docker 容器化时把第 5.2 节的虚拟环境依赖清单(requirements 或 pyproject 锁文件)作为镜像构建的输入,实现"环境即制品"。
⚠️ 常见坑:在 async 端点里直接调阻塞库(requests、同步驱动)冻结整个事件循环(8.2 节陷阱);开发服务器直接上生产;路由顺序把宽路径放在窄路径前吞掉匹配;忘了给 pydantic 模型加默认值导致必填字段把老客户端打挂。
💡 关键直觉:挑框架先问 IO 形态——请求处理大部分时间在等谁(数据库?下游 API?),等得多就异步化收益大;纯计算就回到多 worker 进程模型。
再补一段把 Web 知识串成完整链路的视角。一个生产接口的一生:客户端发起请求,反向代理做加密终结与限流,应用服务器把请求交给程序,中间件链处理认证、日志、跨域,路由命中端点函数,pydantic 校验并转换输入,业务逻辑访问数据库(IO 等待时事件循环让位),响应序列化返回。调试时沿着这条链二分定位:直连应用端口能通、走代理不通,问题在代理层;应用日志显示校验失败,问题在请求体。缓存是这条链上最常见的加速手段,也最容易引入改了数据不生效的陈旧问题——缓存失效策略(过期时间、主动清理)要在设计期想清楚,而不是出事后再补。接口版本化同理:路径里带版本号最直白,升级期新旧并存,下线旧版要有公告与监控。这些工程细节不在任何一段代码里,却决定了服务能不能从演示长成生产。
下一节讲 Web 的"取"的一侧——爬虫与自动化:把第 7 章的重试装饰器与第 8 章的并发选型直接变成生产力。