【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 |
 └─────────────────────────────────────────┘   └──────────────────┘    
相关推荐
小山不高1 天前
nest中如何对typeorm 的repo设置总的center
nestjs
bug_marker14 天前
BullMq sleep job是否会阻塞worker中的其他的jobs
nestjs
濮水大叔20 天前
快来玩玩便捷、高效的Demo练习场
typescript·nodejs·nestjs
木西20 天前
Nest.js实战:构建聊天室的群聊与私聊模块
前端·后端·nestjs
LannyChung1 个月前
NestJS定时器之@Cron
nestjs
None3211 个月前
【NestJs】集成Prisma数据库配置
nestjs
濮水大叔1 个月前
你认为Vonajs提供的这些特性会比Nestjs更好用吗?
nodejs·nestjs
前端卧龙人1 个月前
你的nest项目不要再使用console.log
nestjs