Skip to content

请求完整链路

本章以一个具体的 API 请求为例,详细描述从浏览器发起 HTTP 请求到返回响应的完整链路,帮助开发者理解每一层的职责和数据流转方式。

请求链路总览

链路概览

一个典型的 API 请求经过以下阶段:浏览器 → Nginx → Tornado → Handler 生命周期(set_default_headers → prepare → handler_method → on_finish)→ 响应返回浏览器

以下以 POST /api/v1/position/add 添加岗位为例,逐步拆解完整链路。

Tornado 请求处理模型

Tornado 通过 Handler 生命周期方法装饰器 实现横切关注点:

┌─────────────────────────────────────────────────────────────────┐
│                    Tornado Handler 生命周期                       │
│                                                                  │
│  ① set_default_headers()   → 设置 CORS 头 + trace_id            │
│  ② prepare()               → @login_required(JWT 认证)         │
│  ③ handler method          → @permission_required + @check_demo  │
│                              + @operation_log + 业务逻辑          │
│  ④ on_finish()             → db.close()(释放数据库会话)         │
│                                                                  │
│  异常时:write_error()     → 统一异常响应 + 日志记录              │
└─────────────────────────────────────────────────────────────────┘

第一阶段:网络层

1. 浏览器发起请求

javascript
// 前端 Axios 调用
const res = await request({
    url: '/api/v1/position/add',
    method: 'post',
    data: {
        name: '高级工程师',
        status: 1,
        sort: 10
    }
});

前端通过 Axios 发起 HTTP POST 请求,携带:

  • Authorization: Bearer <JWT Token> 请求头
  • Content-Type: application/json 请求头
  • JSON 请求体

2. Nginx 反向代理(生产环境)

nginx
location /api/ {
    proxy_pass http://127.0.0.1:8041;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Nginx 将请求代理到 Tornado 服务,同时附加客户端真实 IP 到 X-Forwarded-For 头。

第二阶段:Handler 生命周期

3. 路由匹配

Tornado 根据 HTTP 方法 + URL 路径匹配到对应的 Handler 类:

python
# src/api/v1/router.py
def build_routes() -> list:
    routes = []
    for module in _RESOURCE_MODULES:
        routes.extend([(V1_PREFIX + path, handler) for path, handler in module.routes])
    routes.append((V1_PREFIX + "/health", HealthHandler))
    return routes

路由表中 POST /api/v1/position/add 匹配到 PositionAddHandler

4. set_default_headers() — 设置响应头

python
# src/core/base_handler.py
class BaseHandler(tornado.web.RequestHandler):
    def set_default_headers(self):
        super().set_default_headers()
        cros_required(self)     # CORS 跨域头
        apply_trace_id(self)    # 链路追踪 ID
  • CORS:检查 Origin 头是否在白名单中,设置 Access-Control-Allow-* 响应头
  • trace_id:生成唯一追踪 ID,写入响应头和上下文变量

5. prepare() — JWT 认证

python
# src/core/base_handler.py
class BaseHandler(tornado.web.RequestHandler):
    @login_required
    def prepare(self):
        pass

login_required 装饰器在 prepare() 阶段执行认证:

python
# src/common/middleware/authentication.py
def login_required(func):
    def wrapper(self, *args, **kwargs):
        request_url = self.request.path
        # 白名单(/login、/captcha)和 OPTIONS 预检直接放行
        if request_url not in IGNORE_URL and self.request.method != "OPTIONS":
            # 1. 从 Authorization 头提取 Bearer Token
            success, access_token, _ = get_access_token(self.request)
            if not success or not access_token:
                return R.failed(self, code=401, msg="登录过期")
            # 2. JWT 解密验证(签名 + 有效期)
            result = parse_payload(access_token)
            if result["code"] != 0:
                return R.failed(self, code=401, msg="登录过期")
            # 3. 黑名单校验(Redis,不可用时 fail-open 放行)
            if is_token_blacklisted(access_token):
                return R.failed(self, code=401, msg="登录过期")
            # 4. 将用户信息写入上下文变量
            data = result["data"]
            current_user_id.set(int(data["userId"]))
            current_username.set(data.get("username", ""))
            current_realname.set(data.get("realname", ""))
        return func(self, *args, **kwargs)
    return wrapper

认证失败时直接返回 {"code": 401, "msg": "登录过期"},不进入后续 Handler 方法。

6. Handler 方法 — 权限校验 + 业务处理

python
# src/modules/position/handlers.py
class PositionAddHandler(BaseHandler):
    @permission_required("sys:position:add")   # ① RBAC 权限校验
    @check_demo                                 # ② 演示模式拦截
    @operation_log("岗位管理", "添加")           # ③ 操作日志记录
    async def post(self):
        return await position_service.add(self)  # ④ 调用 Service 层

装饰器执行顺序(从外到内):

① permission_required → 校验用户是否拥有 sys:position:add 权限
   ├── user_id == 1(admin)→ 直接放行
   └── 其他用户 → 查询权限列表(Redis 缓存)→ 匹配则放行,否则返回 403

② check_demo → TORNADO_DEMO=True 时返回 "演示环境,暂无操作权限"

③ operation_log → 记录操作日志(请求参数、操作人、IP、耗时等)

④ handler method → 调用 position_service.add(self)

7. Service 层 — 业务逻辑

python
# src/core/base_service.py
class BaseService:
    async def add(self, handler) -> R:
        try:
            # 1. 解析请求体 JSON + Pydantic 校验
            form = self._validate(handler, self.create_schema)
            data = form.model_dump()
            # 2. 唯一性校验
            err = self._check_unique(form)
            if err:
                return R.failed(handler, err)
            # 3. 前置扩展钩子
            self._before_add(handler, data)
            # 4. 组装入库字段
            fields = self._build_create_fields(data)
            # 5. 调用 Repository 创建记录
            self.repo.create(self.model(**fields))
            # 6. 返回成功响应
            return R.ok(handler, msg="添加成功")
        finally:
            self.repo.close()

8. Repository 层 — 数据访问

python
# src/core/base_repository.py
class BaseRepository:
    def create(self, obj: T) -> T:
        # 自动写入创建人
        if hasattr(obj, "create_user"):
            obj.create_user = str(self._uid())
        self.db.add(obj)
        self.db.commit()
        return obj

9. 响应返回

python
# src/core/response.py
def ok(self, data=None, msg="操作成功", code=0, **kwargs):
    body = {"code": code, "data": data, "msg": msg, "ok": True}
    if kwargs:
        body.update(kwargs)
    self._api_result = body
    self.finish(body)  # Tornado 写出 JSON 响应

R.ok() / R.failed() 内部调用 self.finish(body),Tornado 自动序列化为 JSON 并发送 HTTP 响应。

10. on_finish() — 释放资源

python
# src/core/base_handler.py
class BaseHandler(tornado.web.RequestHandler):
    def on_finish(self):
        try:
            db.close()  # 兜底关闭数据库会话
        except Exception:
            pass

请求结束后关闭数据库会话,避免长连接泄漏。Service 层的 finally 块已关闭时此处为幂等 no-op。

11. 异常处理

python
# src/core/base_handler.py
class BaseHandler(tornado.web.RequestHandler):
    def write_error(self, status_code, **kwargs):
        exc = kwargs.get("exc_info", (None, None, None))[1]
        if isinstance(exc, BaseAppError):
            # 业务异常:按类型分级记录日志
            self.set_status(exc.status_code)
            self.finish({"code": exc.code, "data": None, "msg": exc.msg, "ok": False})
        else:
            # 未预期异常:记录完整堆栈
            self.set_status(500)
            self.finish({"code": 500, "data": None, "msg": "服务器内部错误", "ok": False})
异常类型触发场景响应
AuthErrorJWT 过期 / 无权限{"code": 401, "msg": "登录过期"}
PermissionDeniedError权限不足{"code": 403, "msg": "权限不足"}
ValidationErrorPydantic 校验失败{"code": 1, "msg": "field: message"}
BusinessError业务逻辑错误{"code": 1, "msg": "..."}
NotFoundError记录不存在{"code": 404, "msg": "记录不存在"}
Exception未捕获异常{"code": 500, "msg": "服务器内部错误"}

完整链路流程图

[浏览器]
   │ POST /api/v1/position/add
   │ Authorization: Bearer <token>

[Nginx] ── 反向代理转发


[Tornado 路由匹配] ── 匹配 PositionAddHandler


[set_default_headers()] ── 设置 CORS 头 + trace_id


[prepare() + @login_required] ── JWT 认证
   │  ├─ 白名单(/login、/captcha)→ 跳过
   │  ├─ Token 无效/过期 → 返回 {code:401, msg:"登录过期"}
   │  ├─ Token 在黑名单中 → 返回 {code:401, msg:"登录过期"}
   │  └─ 认证通过 → current_user_id / current_username / current_realname 写入上下文


[@permission_required("sys:position:add")] ── RBAC 权限校验
   │  ├─ user_id == 1 → 直接放行
   │  ├─ 权限不足 → 返回 {code:403, msg:"权限不足"}
   │  └─ 权限通过 → 继续


[@check_demo] ── 演示模式拦截
   │  ├─ TORNADO_DEMO=True → 返回 {code:1, msg:"演示环境,暂无操作权限"}
   │  └─ 非演示模式 → 继续


[@operation_log("岗位管理", "添加")] ── 操作日志装饰器


[async def post(self)] ── Handler 方法


[position_service.add(self)] ── Service 层
   ├─ parse_json(handler) ── 解析请求体 JSON
   ├─ validate(schema, data) ── Pydantic 校验
   ├─ _check_unique(form) ── 唯一性校验(DB 查询)
   ├─ _before_add(handler, data) ── 前置扩展钩子
   ├─ _build_create_fields(data) ── 组装入库字段
   └─ repo.create(model(**fields)) ── Repository 层


   [BaseRepository.create(obj)]
       ├─ obj.create_user = str(uid) ── 自动写入创建人
       ├─ db.add(obj) + db.commit() ── 提交事务
       └─ return obj


[R.ok(handler, msg="添加成功")] ── 统一响应
   ├─ self._api_result = body ── 保存结果供日志解析
   └─ self.finish(body) ── 写出 JSON 响应


[on_finish()] ── 释放资源
   └─ db.close() ── 关闭数据库会话


[浏览器] ← { "code": 0, "data": null, "msg": "添加成功", "ok": true }

总结

Tornado 的请求处理通过 Handler 生命周期方法(set_default_headerspreparehandler_methodon_finish)和装饰器(@permission_required@check_demo@operation_log)实现横切关注点。认证在 prepare() 阶段完成,权限/演示模式/操作日志通过装饰器在 Handler 方法执行前校验,数据库会话在 on_finish() 中兜底关闭。这种设计使得每一层职责单一、可独立测试。

小蚂蚁云团队 · 提供技术支持