【Nest指北系列-源码】(七)请求生命周期

上一篇【Nest指北系列-源码】(六)创建应用实例和初始化应用 讲到 Nest 中路由注册流程,通过 Reflect.getMetadata() 获取控制器中 @Get()@Post() 等装饰器定义的路径请求方法请求处理函数等元数据,然后绑定到 HTTP 服务器的路由系统中。

在 Nest 中,一个请求从进入应用到最终响应,除了执行请求处理函数外,还会依次经过很多处理步骤,完整链路如下:

Middleware -> Guards -> Interceptors(before) -> Pipes -> Controller handler -> Interceptors(after) -> Exception Filters

本文将会分析这条执行链是如何构建的。


先回到 Nest 项目的入口文件:

ts 复制代码
// main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

其中,NestFactory.create() 创建 app 实例,然后通过 app.listen() 监听端口。listen 方法中先调用 init 方法初始化,再调用底层 HTTP 服务器的 listen 方法。比如底层服务器为 Express 的话,则进入:

ts 复制代码
@Injectable()
export class ExpressAdapter extends AbstractHttpAdapter {
  listen() { this.httpServer.listen(...) }
}

之后对于每个请求,会调用其路径对应的 Nest 路由代理

Nest 路由代理

Nest 路由代理是什么呢?让我们来看创建它,并将它绑定到底层 HTTP 框架的源码。

源码路径:packages/core/router/router-explorer.ts

上一篇【Nest指北系列-源码】(六)创建应用实例和初始化应用中提到了 applyPathsToRouterProxy() 方法,其中对于每一个路由定义调用 applyCallbackToRouter() 方法。

applyCallbackToRouter

桥梁函数,负责创建 Nest 路由代理,并将它注册到底层框架中。

精简后的源码如下:

ts 复制代码
private applyCallbackToRouter<T extends HttpServer>(
  router: T,
  routeDefinition: RouteDefinition,
  instanceWrapper: InstanceWrapper,
  moduleKey: string,
  routePathMetadata: RoutePathMetadata,
  host: string | RegExp | Array<string | RegExp>,
) {
  const {
    path: paths,
    requestMethod,
    targetCallback,
    methodName,
  } = routeDefinition;

  const { instance } = instanceWrapper;
  const routerMethodRef = this.routerMethodFactory
    .get(router, requestMethod)
    .bind(router);

  // ......
  const proxy = this.createCallbackProxy(
    instance,
    targetCallback,
    methodName,
    moduleKey,
    requestMethod,
  );

  // ......
  let routeHandler = this.applyHostFilter(host, proxy);

  paths.forEach(path => {
    // ......

    routePathMetadata.methodPath = path;
    const pathsToRegister = this.routePathFactory.create(
      routePathMetadata,
      requestMethod,
    );
    pathsToRegister.forEach(path => {
      // ......

      const normalizedPath = router.normalizePath
        ? router.normalizePath(path)
        : path;
      routerMethodRef(normalizedPath, routeHandler);

      // ......
    });

    // ......
  });
}

参数

  • router:http 服务器适配器实例,比如 Express 或 Fastify 的包装类。

  • routeDefinition:路由定义。

  • InstanceWrapper:控制器的 InstanceWrapper 实例。

  • moduleKey:模块唯一标识。

  • routePathMetadata: 路由 path 元数据,包含模块路径、控制器路径。

核心流程

1. 通过 routerMethodFactory.get() 获取 http adapter 中的对应处理方法

可以看下 RouterMethodFactory 类的实现:

ts 复制代码
export const REQUEST_METHOD_MAP = {
  [RequestMethod.GET]: 'get',
  [RequestMethod.POST]: 'post',
  [RequestMethod.PUT]: 'put',
  [RequestMethod.DELETE]: 'delete',
  [RequestMethod.PATCH]: 'patch',
  [RequestMethod.ALL]: 'all',
  // ......
} as const satisfies Record<RequestMethod, keyof HttpServer>;

export class RouterMethodFactory {
  public get(target: HttpServer, requestMethod: RequestMethod): Function {
    const methodName = REQUEST_METHOD_MAP[requestMethod];
    const method = target[methodName];
    if (!method) {
      return target.use;
    }
    return method;
  }
}

通过一个映射表把 Nest 的 RequestMethod 枚举转换为字符串方法名,然后在 http adapter 上获取对应处理方法。

2. 创建 Nest 路由代理

Nest 不直接调用控制器原始方法,而是调用 createCallbackProxy 方法封装一个代理。它的具体实现下面会讲到。

3. 将路由代理注册到底层框架中

  • routePathFactory.create(...) 负责合并模块、控制器和方法路径。

  • normalizePath() 会根据 Express/Fastify 规范化路径。

  • 最后调用 routerMethodRef(path, handler)router.get(path, handler) 之类的方法完成注册。

createCallbackProxy

创建路由代理,其中处理 GuardsInterceptorsPipesException Filters

ts 复制代码
private createCallbackProxy(
  instance: Controller,
  callback: RouterProxyCallback,
  methodName: string,
  moduleRef: string,
  requestMethod: RequestMethod,
  contextId = STATIC_CONTEXT,
  inquirerId?: string,
) {
  const executionContext = this.executionContextCreator.create(
    instance,
    callback,
    methodName,
    moduleRef,
    requestMethod,
    contextId,
    inquirerId,
  );
  const exceptionFilter = this.exceptionsFilter.create(
    instance,
    callback,
    moduleRef,
    contextId,
    inquirerId,
  );
  return this.routerProxy.createProxy(executionContext, exceptionFilter);
}

参数

  • instance: 控制器实例。

  • callback: 控制器上的 handler 方法。

  • methodName: 路由方法名。

  • moduleRef: 模块唯一标识。

  • requestMethod: 请求方法类型(GET / POST等)。

核心流程

  1. 调用 executionContextCreator.create(...) 创建一个 executionContext 对象,即最终的路由处理函数,内部集成了 handler 函数、守卫、拦截器、管道、响应头、状态码处理。可以理解为,它负责生成一个完整的执行链上下文

  2. 调用 exceptionsFilter.create(...) 创建一个 exceptionFilter 对象,即异常处理函数,内部包含全局默认异常过滤器以及自定义的异常过滤器。用于包装上一步构建的路由处理函数,当它抛出异常时,统一由异常处理函数处理。

  3. 调用 routerProxy.createProxy(...) 将路由处理链和异常处理链包装成最终的路由代理。

executionContext

构建路由处理链。

核心流程

1. 通过 Reflecet.getMetadata() 获取控制器 handler 方法的元数据

比如:

  • http 状态码(@HttpCode)。
  • 参数列表长度(argsLength)。
  • 参数类型数组(用于管道验证)。
  • 参数装饰器 @Body@Param 等的映射。
  • 响应头信息。
ts 复制代码
const {
  argsLength,
  fnHandleResponse,
  paramtypes,
  getParamsMetadata,
  httpStatusCode,
  responseHeaders,
  hasCustomHeaders,
} = this.getMetadata(
  instance,
  callback,
  methodName,
  moduleKey,
  requestMethod,
  contextType,
);

2. 将参数元数据和参数类型数组合并为 paramOptions 结构,供管道使用

ts 复制代码
const paramsOptions = this.contextUtils.mergeParamsMetatypes(...);

3. 构建组件链

ts 复制代码
const pipes = this.pipesContextCreator.create(...);
const guards = this.guardsContextCreator.create(...);
const interceptors = this.interceptorsContextCreator.create(...);
  • 根据控制器 handler 方法上的装饰器(如 @UsePipes() 等)创建执行链。这些 pipesguardsinterceptors 都是实例化后的,每次请求都会执行。

4. 构建逻辑函数

ts 复制代码
const fnCanActivate = this.createGuardsFn(...);
const fnApplyPipes = this.createPipesFn(...);

将上一步获取的 guardspipes 数组封装成函数,比如会取 guard 上的 canActive 方法。

5. 构建 handler 包裹器

ts 复制代码
const handler = (args, req, res, next) => async () => {
  fnApplyPipes && (await fnApplyPipes(args, req, res, next));
  return callback.apply(instance, args);
};

对控制器 handler 方法的封装,会先执行管道处理函数,再执行控制器方法

6. 生成最终执行函数

ts 复制代码
return async (req, res, next) => {
  const args = this.contextUtils.createNullArray(argsLength);

  fnCanActivate && (await fnCanActivate([req, res, next]));  // 守卫判断

  this.responseController.setStatus(res, httpStatusCode);
  if (hasCustomHeaders) {
    this.responseController.setHeaders(res, responseHeaders);
  }

  const result = await this.interceptorsConsumer.intercept(
    interceptors,
    [req, res, next],
    instance,
    callback,
    handler(args, req, res, next),  // 包含管道逻辑的实际 handler
    contextType,
  );

  await (fnHandleResponse as HandlerResponseBasicFn)(result, res, req); // 最终返回
};

其中按顺序:

  • 执行守卫判断。
  • 设置状态码与响应头。
  • 执行拦截器链。
  • 执行包含管道 逻辑的实际控制器 handler 方法。
  • 最终将返回值通过响应控制器输出到 res。

可以看出,executionContext.create(...) 返回了最终的路由处理函数,内部集成了守卫、拦截器、管道、响应头、状态码处理,整体执行顺序为:guards -> interceptors -> pipes -> handler

exceptionFilter

构建异常处理器链。

核心流程

1. 创建默认异常处理器

ts 复制代码
const exceptionHandler = new ExceptionsHandler(this.applicationRef);
  • applicationRef 是对 Express / Fastify Response API 的抽象封装。

  • ExceptionsHandler 是 Nest 默认的异常处理器类,内部提供 next.handle(exception, host) 方法。

2. 读取装饰器元数据构建上下文链

ts 复制代码
const filters = this.createContext(
  instance,
  callback,
  EXCEPTION_FILTERS_METADATA,
  contextId,
  inquirerId,
);
  • 通过 ContextCreator 类的方法,根据 @UseFilters() 装饰器获取控制器类和方法上注册的异常过滤器,创建上下文链。

3. 如果没有注册自定义过滤器,返回默认异常处理器

ts 复制代码
if (isEmpty(filters)) {
  return exceptionHandler;
}
  • 如果没有自定义的 @UseFilters(),就返回默认 handler。

4. 注册自定义过滤器链

ts 复制代码
exceptionHandler.setCustomFilters(filters.reverse());
  • 使用 .reverse() 让后定义的过滤器 先执行(类似中间件执行顺序)。

  • 注册后,异常处理器在处理异常时会按顺序调用这些 filterscatch(exception, host) 方法。

5. 返回最终异常处理器 handler

ts 复制代码
return exceptionHandler;
  • 返回的 handler 将被用于包装前面构建的路由处理链的执行过程(如果执行失败,会进入 exceptionHandler)。

createProxy

将目标函数(路由 handler 函数)包装成带有异常处理的路由代理函数,用于最终绑定到底层框架中。

ts 复制代码
public createProxy(
  targetCallback: RouterProxyCallback,
  exceptionsHandler: ExceptionsHandler,
) {
  return async <TRequest, TResponse>(
    req: TRequest,
    res: TResponse,
    next: () => void,
  ) => {
    try {
      await targetCallback(req, res, next);
    } catch (e) {
      const host = new ExecutionContextHost([req, res, next]);
      exceptionsHandler.next(e, host);
      return res;
    }
  };
}

参数

  • targetCallback:路由处理函数(含守卫/拦截器/管道等)。

  • exceptionsHandler:异常处理函数。

核心逻辑

  1. try..catch... 包裹路由处理函数,catch 捕获异常,执行异常处理链。

  2. ExecutionContextHost 是一个包装器,将 [req, res, next] 提供给异常处理器,供其调用 getRequest()getResponse() 等 API。

  3. 调用 exceptionsHandler.next(...) 会遍历注册的异常过滤器,尝试依次调用其 catch(exception, host) 方法。


分析完 applyCallbackToRoutercreateCallbackProxy 方法,可以理解 Nest 中每个路由回调是如下形式:

ts 复制代码
app.get('/users', async (req, res, next) => {
  try {
    // 守卫 → 拦截器 → 管道 → 控制器方法
  } catch (e) {
    // 自定义异常过滤器
  }
});

整条链路中还差一个中间件,它是在什么阶段处理的呢?我们继续来看。

中间件处理

关于中间件的注册,我们要回到 NestApplication 应用实例的 init 方法。其中调用 registerModules -> this.middlewareModule.register -> resolveMiddleware。使用 expressApp.use(...) 将中间件注册到底层框架。

ts 复制代码
public async register(
  middlewareContainer: MiddlewareContainer,
  container: NestContainer,
  config: ApplicationConfig,
  injector: Injector,
  httpAdapter: HttpServer,
  graphInspector: GraphInspector,
  options: TAppOptions,
) {
  // ......

  const modules = container.getModules();
  await this.resolveMiddleware(middlewareContainer, modules);
}

public async resolveMiddleware(
  middlewareContainer: MiddlewareContainer,
  modules: Map<string, Module>,
) {
  const moduleEntries = [...modules.entries()];
  const loadMiddlewareConfiguration = async ([moduleName, moduleRef]: [
    string,
    Module,
  ]) => {
    await this.loadConfiguration(middlewareContainer, moduleRef, moduleName);
    await this.resolver.resolveInstances(moduleRef, moduleName);
  };
  await Promise.all(moduleEntries.map(loadMiddlewareConfiguration));
}

于是,每次请求进入之后会先执行中间件。中间件不在 Nest 创建的路由代理中。


总结

最终,Nest 请求的整个生命周期是这样的:

ts 复制代码
                            Incoming HTTP Request
                                     |
                        Http Adapter (Express/Fastify)
                                     |
                                 Middleware
                                     |
                                handler proxy
                                     |
                      ---------------------------------------
                     |                                  |
 ┌─────────────────────────────────────────┐   ┌──────────────────┐ 
 │ 路由处理链                                |   │ 异常处理链        |
 | Guard → Interceptor → Pipe → Controller |   | Exception Filter |
 └─────────────────────────────────────────┘   └──────────────────┘    
相关推荐
亮子AI20 天前
【NestJS】为什么return不返回客户端?
前端·javascript·git·nestjs
小p20 天前
nestjs学习2:利用typescript改写express服务
nestjs
Eric_见嘉1 个月前
NestJS 🧑‍🍳 厨子必修课(九):API 文档 Swagger
前端·后端·nestjs
XiaoYu20021 个月前
第3章 Nest.js拦截器
前端·ai编程·nestjs
XiaoYu20021 个月前
第2章 Nest.js入门
前端·ai编程·nestjs
实习生小黄1 个月前
NestJS 调试方案
后端·nestjs
当时只道寻常1 个月前
NestJS 如何配置环境变量
nestjs
濮水大叔2 个月前
VonaJS是如何做到文件级别精确HMR(热更新)的?
typescript·node.js·nestjs
ovensi2 个月前
告别笨重的 ELK,拥抱轻量级 PLG:NestJS 日志监控实战指南
nestjs