Kodokon kodokon.com

输入校验与错误中间件

在 API 的边缘校验数据,并把所有错误处理集中到一个四参数的中间件里。

9 分钟 · 3 题

在 Kodokon 中打开本课

每一份传入的数据在被证明清白之前都是敌意的:JSON 请求体、URL 参数、请求头。校验应放在应用的边缘,也就是控制器之前,好让内层能在有保障的数据上工作。接着要区分两类错误:运营型错误(无效输入、资源缺失、冲突),它们是预期之内的,会被翻译成 4xx;以及编程型错误(bug),它们必须在客户端一侧产生一个不透明的 500,在服务器一侧留下完整的堆栈跟踪。

JAVASCRIPT
export class HttpError extends Error {
  constructor(status, message, details = undefined) {
    super(message);
    this.status = status;
    this.details = details;
  }
}
一个携带自身 HTTP 状态的错误:最基础的构件。

一个校验中间件会在控制器之前拦截请求。下面这个手写版本展示了原理;在生产环境中,一个声明式的 schema(zod 已成为标准)还额外提供类型推断和结构化的消息。它的取舍是:多一个依赖和一点运行时开销,换来集中、可组合、不可能忘记的规则。

JAVASCRIPT
import { HttpError } from './http-error.js';

export function validateUser(req, res, next) {
  const { email, name } = req.body ?? {};
  const errors = [];
  if (typeof email !== 'string' || !email.includes('@')) {
    errors.push('email: invalid format');
  }
  if (typeof name !== 'string' || name.trim().length < 2) {
    errors.push('name: 2 characters minimum');
  }
  if (errors.length > 0) {
    return next(new HttpError(400, 'Invalid body', errors));
  }
  next();
}
用 router.post('/', validateUser, controller.create) 把它接上。

Express 通过一个中间件的四参数签名 (err, req, res, next) 来识别它是错误中间件。用 app.use(errorHandler) 注册在所有路由之后、位于最末尾,它就成了 API 唯一的错误出口:统一的响应格式、集中于一处的日志、不泄露任何内部信息。而各个控制器只需调用 next(err)

JAVASCRIPT
export function errorHandler(err, req, res, next) {
  if (res.headersSent) {
    return next(err);
  }
  const status = err.status ?? 500;
  const body = { error: err.message };
  if (err.details) {
    body.details = err.details;
  }
  if (status >= 500) {
    console.error(err);
    body.error = 'Internal server error';
  }
  res.status(status).json(body);
}
4xx 对客户端透明,500 被掩盖但会被记录。

知识检测

确认你已牢记本课的重点内容。

  1. Express 如何把错误中间件和普通中间件区分开?
    • 通过它的名字,名字里必须含有 error 一词
    • 通过它的四参数签名 (err, req, res, next)
    • 通过它排在链条最前面的位置
    • 通过对 app.setErrorHandler 的显式调用
  2. 对于一个意料之外的编程型错误,正确的 HTTP 响应是什么?
    • 一个带有消息和堆栈的 500,以帮助诊断
    • 一个 400,表示请求失败了
    • 一个带有通用消息的 500,完整的错误则记录在服务器一侧
  3. 在 Express 4 中,如果一个异步处理器在没有 try/catch 的情况下被拒绝,会发生什么?
    • 错误照样会抵达错误中间件
    • 请求会一直挂起:拒绝永远不会被转发给 next
    • Express 自动返回一个 400
    • Node 进程立即停止