Skip to content

好的,我们来探讨一个在构建现代应用中必不可少的功能:发送 HTTP 请求。这就像是为你的应用配备一部“电话”,让它可以主动联系和调用外部世界或其他内部微服务的功能。

为你的应用配备一部“超级电话”:NestJS HTTP 模块深度解析

想象你的 NestJS 应用是一个智能的个人助理。

它的核心工作是响应你的指令(处理传入的 HTTP 请求)。但要成为一个真正有用的助理,它必须具备主动与外界沟通的能力:

  • 查询天气:需要给“天气预报中心”(一个外部的天气 API)打电话。
  • 获取新闻:需要给“新闻通讯社”(另一个外部 API)打电话。
  • 与内部部门协作:如果你的应用是微服务架构,它可能需要给“用户服务部门”或“订单服务部门”(其他的内部微生务)打电话,来协同完成一个复杂的任务。

在程序的世界里,这部“电话”就是 HTTP 客户端。它能让你的应用作为客户端 (Client),去请求其他服务器上的资源。

NestJS 官方提供了一个 @nestjs/axios 模块,它将强大而流行的 Axios 库与 NestJS 的依赖注入系统和可观测流 (Observables) 进行了完美的封装。

1. 第一步:安装“电话机” (@nestjs/axios)

第一步:安装必要的依赖

bash
npm install @nestjs/axios axios

第二步:在模块中注册 HttpModule

你需要在使用“电话”的那个模块中,导入 HttpModule

src/weather/weather.module.ts

typescript
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { WeatherService } from './weather.service';
import { WeatherController } from './weather.controller';

@Module({
  imports: [
    // 注册 HttpModule,就像给这个部门装上电话线
    HttpModule,
  ],
  controllers: [WeatherController],
  providers: [WeatherService],
})
export class WeatherModule {}

2. 第二步:拿起电话,开始呼叫 (HttpService)

注册完成后,我们就可以在服务中注入 HttpService,它就是我们那部功能齐全的“电话机”。

HttpService 的方法(如 get, post, put 等)返回的是一个 RxJS 的 Observable

前置知识:Observable 是什么?

你可以把它想象成一个“未来的数据流”。

  • Promise(它代表一个单一的未来值)不同,Observable 可以代表多个未来的值(尽管在 HTTP 请求中,它通常只发出一个值,即响应)。
  • 它非常强大,可以被轻松地转换、组合和处理。但对于初学者,最重要的一点是:你需要一种方式来从这个“数据流”中取出最终的那个值。

src/weather/weather.service.ts

typescript
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { Observable, map, firstValueFrom } from 'rxjs';
import { AxiosResponse } from 'axios';

interface WeatherData {
  // ... 定义天气数据的结构
}

@Injectable()
export class WeatherService {
  // 1. 注入 HttpService
  constructor(private readonly httpService: HttpService) {}

  // 方案一:返回 Observable (NestJS Controller 会自动订阅)
  getWeatherForCity(city: string): Observable<AxiosResponse<WeatherData>> {
    console.log(`📞 正在呼叫天气 API,查询城市: ${city}`);
    const apiKey = 'YOUR_WEATHER_API_KEY';
    const url = `https://api.weather.com/v1/current?city=${city}&key=${apiKey}`;

    // 2. 使用 httpService.get() 发起请求,它返回一个 Observable
    return this.httpService.get<WeatherData>(url);
  }

  // 方案二:使用 async/await (更符合常规的异步编程习惯)
  async getWeatherForCityAsync(city: string): Promise<WeatherData> {
    console.log(`📞 (Async) 正在呼叫天气 API,查询城市: ${city}`);
    const apiKey = 'YOUR_WEATHER_API_KEY';
    const url = `https://api.weather.com/v1/current?city=${city}&key=${apiKey}`;

    const observable = this.httpService.get<WeatherData>(url).pipe(
      map((response) => response.data) // 3. 使用 pipe() 和 map() 操作符只提取需要的数据部分
    );

    // 4. 使用 firstValueFrom() 将 Observable 转换为 Promise
    const weatherData = await firstValuefrom(observable);
    return weatherData;
  }
}

代码分析:

  1. 返回 Observable: 这是最“原生”的方式。如果你的 Controller 直接返回这个 Observable,NestJS 框架会自动订阅它,等待数据返回,然后将其发送给客户端。
  2. 转换为 Promise: 对于许多习惯了 async/await 的开发者来说,这种方式更自然。
    • .pipe(): Observable 的一个核心方法,它允许你像接水管一样,将一系列“操作符”串联起来处理数据流。
    • map(response => response.data): map 是一个非常常用的操作符,它接收上一步流过来的数据(这里是完整的 AxiosResponse 对象),然后将其转换为你想要的格式(这里我们只提取了 data 属性)。
    • firstValueFrom(): 这是一个辅助函数,它会订阅 Observable,等待它发出第一个值,然后将这个值包装成一个 Promise 返回,之后就自动取消订阅。对于 HTTP 请求这种“一次性”的场景,它非常完美。

3. “超级电话”的高级功能:配置 HttpModule

你可以对 HttpModule 进行配置,来设置一些全局的默认选项,比如超时时间、默认请求头等。

typescript
// weather.module.ts
@Module({
  imports: [
    HttpModule.register({
      timeout: 5000, // 超时时间 5 秒
      maxRedirects: 5, // 最大重定向次数
      headers: {
        // 默认请求头
        'X-Custom-Header': 'my-value',
      },
    }),
  ],
  // ...
})
export class WeatherModule {}

如果你需要根据 ConfigService 动态配置,可以使用 HttpModule.registerAsync,其用法与我们之前学过的其他模块(如 TypeOrmModule)完全一致。

4. 中国企业级方案的思考

虽然 @nestjs/axios 功能强大,但在复杂的、高并发的中国企业级微服务环境中,开发者往往会遇到一些原生方案覆盖不足的痛点,并会考虑更专业的解决方案。

痛点分析:

  1. 服务发现与负载均衡: 在微服务架构中,一个服务(如 user-service)可能有多个实例在运行。我们不应该将请求硬编码到某个具体的 IP 地址上,而是希望能请求一个虚拟的服务名,由一个“注册中心”(如 Nacos, Consul)来告诉我们当前哪些实例是健康的,并以某种策略(如轮询、随机)来分配请求。
  2. 熔断与降级: 如果天气 API 突然崩溃了,我们不希望所有的请求都涌向它,导致我们的应用也因为大量请求超时而被拖垮。熔断器 (Circuit Breaker) 就像一个智能的保险丝,当它发现某个服务的错误率过高时,会自动“跳闸”,在一段时间内直接拒绝发往该服务的请求,并可以返回一个预设的“降级”数据(比如一个默认的天气信息),从而保护我们的主应用。
  3. 重试机制: 对于网络抖动等临时性错误,自动进行有限次数的重试是非常有必要的。
  4. 更复杂的认证与链路追踪: 在复杂的调用链中,需要统一处理认证凭证的传递,并植入链路追踪 ID (Trace ID)。

企业级 Demo 思路:封装一个更强大的 AdvancedHttpService

我们可以基于 @nestjs/axios,通过装饰器模式或继承,封装一个更符合企业级需求的 AdvancedHttpService

src/advanced-http/advanced-http.service.ts

typescript
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { NacosService } from '../nacos/nacos.service'; // 假设我们有一个 Nacos 服务发现的服务
import * as CircuitBreaker from 'opossum'; // 引入熔断器库

@Injectable()
export class AdvancedHttpService {
  private breakers: Map<string, CircuitBreaker> = new Map();

  constructor(
    private readonly httpService: HttpService,
    private readonly nacosService: NacosService
  ) {}

  // 封装 get 方法
  async get(serviceName: string, path: string, options?: any) {
    // 1. 获取或创建该服务的熔断器
    const breaker = this.getOrCreateBreaker(serviceName);

    // 2. 使用熔断器的 .fire() 方法来包装我们的请求
    return breaker.fire(async () => {
      // 3. 通过 Nacos 进行服务发现和负载均衡,获取一个健康的实例地址
      const instanceUrl = await this.nacosService.getInstanceUrl(serviceName);
      const finalUrl = `${instanceUrl}${path}`;

      console.log(`[AdvancedHttp] Firing request to ${finalUrl}`);

      // 4. 使用底层的 HttpService 发送请求
      const { data } = await firstValueFrom(
        this.httpService.get(finalUrl, options)
      );
      return data;
    });
  }

  private getOrCreateBreaker(serviceName: string): CircuitBreaker {
    if (!this.breakers.has(serviceName)) {
      // 5. 为每个服务创建独立的熔断器实例,并配置规则
      const options = {
        timeout: 3000, // 如果请求超过3秒,就算失败
        errorThresholdPercentage: 50, // 如果 50% 的请求失败,就跳闸
        resetTimeout: 30000, // 跳闸后 30 秒再尝试恢复
      };
      const breaker = new CircuitBreaker(
        async (requestFn) => requestFn(),
        options
      );

      // 6. 定义降级逻辑
      breaker.fallback(() => ({
        message: `Service ${serviceName} is currently unavailable. Please try again later.`,
        fromCache: true,
      }));

      this.breakers.set(serviceName, breaker);
    }
    return this.breakers.get(serviceName);
  }
}

使用方式

typescript
// 某个服务里
@Injectable()
export class SomeService {
  constructor(private readonly advancedHttp: AdvancedHttpService) {}

  async getOrderDetails(orderId: string) {
    // 不再关心具体 IP,只请求服务名
    // 熔断、降级、服务发现等都已透明地处理了
    return this.advancedHttp.get('order-service', `/orders/${orderId}`);
  }
}

这个封装后的 AdvancedHttpService 才是许多大型企业真正在微服务架构中使用的“超级电话”,它内置了服务治理的各种核心能力。

总结

@nestjs/axios 为我们提供了一个坚实、好用的基础 HTTP 客户端。

  • 基础用法: 在模块中 imports: [HttpModule],然后在服务中注入 HttpService 并调用其方法。
  • 处理响应: HttpService 方法返回 Observable,你可以直接返回它,或使用 firstValueFrom 配合 async/await 将其转换为 Promise
  • 企业级考量: 在真实的微服务环境中,仅仅发送 HTTP 请求是不够的。你需要在此基础上,整合服务发现负载均衡熔断降级等服务治理能力,来构建一个真正健壮、高可用的系统。

从学会打第一通“电话”,到搭建起一整套智能的“企业通信系统”,掌握 HTTP 请求是后端开发从入门到精通的必经之路。