好的,我们来探讨一个在构建现代应用中必不可少的功能:发送 HTTP 请求。这就像是为你的应用配备一部“电话”,让它可以主动联系和调用外部世界或其他内部微服务的功能。
为你的应用配备一部“超级电话”:NestJS HTTP 模块深度解析
想象你的 NestJS 应用是一个智能的个人助理。
它的核心工作是响应你的指令(处理传入的 HTTP 请求)。但要成为一个真正有用的助理,它必须具备主动与外界沟通的能力:
- 查询天气:需要给“天气预报中心”(一个外部的天气 API)打电话。
- 获取新闻:需要给“新闻通讯社”(另一个外部 API)打电话。
- 与内部部门协作:如果你的应用是微服务架构,它可能需要给“用户服务部门”或“订单服务部门”(其他的内部微生务)打电话,来协同完成一个复杂的任务。
在程序的世界里,这部“电话”就是 HTTP 客户端。它能让你的应用作为客户端 (Client),去请求其他服务器上的资源。
NestJS 官方提供了一个 @nestjs/axios
模块,它将强大而流行的 Axios 库与 NestJS 的依赖注入系统和可观测流 (Observables) 进行了完美的封装。
1. 第一步:安装“电话机” (@nestjs/axios
)
第一步:安装必要的依赖
npm install @nestjs/axios axios
第二步:在模块中注册 HttpModule
你需要在使用“电话”的那个模块中,导入 HttpModule
。
src/weather/weather.module.ts
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
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;
}
}
代码分析:
- 返回
Observable
: 这是最“原生”的方式。如果你的Controller
直接返回这个Observable
,NestJS 框架会自动订阅它,等待数据返回,然后将其发送给客户端。 - 转换为
Promise
: 对于许多习惯了async/await
的开发者来说,这种方式更自然。.pipe()
:Observable
的一个核心方法,它允许你像接水管一样,将一系列“操作符”串联起来处理数据流。map(response => response.data)
:map
是一个非常常用的操作符,它接收上一步流过来的数据(这里是完整的AxiosResponse
对象),然后将其转换为你想要的格式(这里我们只提取了data
属性)。firstValueFrom()
: 这是一个辅助函数,它会订阅Observable
,等待它发出第一个值,然后将这个值包装成一个Promise
返回,之后就自动取消订阅。对于 HTTP 请求这种“一次性”的场景,它非常完美。
3. “超级电话”的高级功能:配置 HttpModule
你可以对 HttpModule
进行配置,来设置一些全局的默认选项,比如超时时间、默认请求头等。
// 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
功能强大,但在复杂的、高并发的中国企业级微服务环境中,开发者往往会遇到一些原生方案覆盖不足的痛点,并会考虑更专业的解决方案。
痛点分析:
- 服务发现与负载均衡: 在微服务架构中,一个服务(如
user-service
)可能有多个实例在运行。我们不应该将请求硬编码到某个具体的 IP 地址上,而是希望能请求一个虚拟的服务名,由一个“注册中心”(如 Nacos, Consul)来告诉我们当前哪些实例是健康的,并以某种策略(如轮询、随机)来分配请求。 - 熔断与降级: 如果天气 API 突然崩溃了,我们不希望所有的请求都涌向它,导致我们的应用也因为大量请求超时而被拖垮。熔断器 (Circuit Breaker) 就像一个智能的保险丝,当它发现某个服务的错误率过高时,会自动“跳闸”,在一段时间内直接拒绝发往该服务的请求,并可以返回一个预设的“降级”数据(比如一个默认的天气信息),从而保护我们的主应用。
- 重试机制: 对于网络抖动等临时性错误,自动进行有限次数的重试是非常有必要的。
- 更复杂的认证与链路追踪: 在复杂的调用链中,需要统一处理认证凭证的传递,并植入链路追踪 ID (Trace ID)。
企业级 Demo 思路:封装一个更强大的 AdvancedHttpService
我们可以基于 @nestjs/axios
,通过装饰器模式或继承,封装一个更符合企业级需求的 AdvancedHttpService
。
src/advanced-http/advanced-http.service.ts
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);
}
}
使用方式:
// 某个服务里
@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 请求是后端开发从入门到精通的必经之路。