NestJS HTTP客户端
学习目标
- 掌握NestJS HTTP模块的使用方法
- 理解HTTP请求的配置选项
- 学习如何处理HTTP响应
- 了解HTTP拦截器的使用场景和实现方法
- 掌握HTTP客户端的错误处理策略
核心知识点
1. HTTP模块简介
NestJS的HTTP客户端基于Axios库,通过@nestjs/axios包提供。它允许我们在NestJS应用中发送HTTP请求到外部API或服务。HTTP模块提供了以下功能:
- 发送GET、POST、PUT、DELETE等HTTP请求
- 配置请求头、参数、超时等选项
- 处理HTTP响应和错误
- 使用拦截器修改请求和响应
- 支持请求取消和重试
2. 安装和配置
首先,我们需要安装HTTP模块:
npm install --save @nestjs/axios axios然后,在需要使用HTTP客户端的模块中导入HTTP模块:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
HttpModule.register({
timeout: 5000,
maxRedirects: 5,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}3. 基本使用
3.1 在服务中使用
// src/app.service.ts
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { lastValueFrom } from 'rxjs';
@Injectable()
export class AppService {
constructor(private readonly httpService: HttpService) {}
async getHello(): Promise<string> {
const response = await lastValueFrom(
this.httpService.get('https://api.example.com/hello'),
);
return response.data;
}
}3.2 发送不同类型的请求
// src/app.service.ts
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { lastValueFrom } from 'rxjs';
@Injectable()
export class AppService {
constructor(private readonly httpService: HttpService) {}
// 发送GET请求
async getUsers(): Promise<any> {
const response = await lastValueFrom(
this.httpService.get('https://api.example.com/users', {
params: { page: 1, limit: 10 },
headers: {
'Authorization': 'Bearer token123',
},
}),
);
return response.data;
}
// 发送POST请求
async createUser(user: any): Promise<any> {
const response = await lastValueFrom(
this.httpService.post('https://api.example.com/users', user, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123',
},
}),
);
return response.data;
}
// 发送PUT请求
async updateUser(id: string, user: any): Promise<any> {
const response = await lastValueFrom(
this.httpService.put(`https://api.example.com/users/${id}`, user),
);
return response.data;
}
// 发送DELETE请求
async deleteUser(id: string): Promise<any> {
const response = await lastValueFrom(
this.httpService.delete(`https://api.example.com/users/${id}`),
);
return response.data;
}
}4. 请求配置
HTTP模块支持以下配置选项:
baseURL: 请求的基础URLtimeout: 请求超时时间(毫秒)maxRedirects: 最大重定向次数headers: 默认请求头paramsSerializer: 参数序列化方法withCredentials: 是否携带凭证auth: 基本认证信息responseType: 响应类型xsrfCookieName: XSRF cookie名称xsrfHeaderName: XSRF头名称
5. 响应处理
HTTP响应包含以下信息:
data: 响应数据status: HTTP状态码statusText: HTTP状态文本headers: 响应头config: 请求配置
// src/app.service.ts
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { lastValueFrom } from 'rxjs';
@Injectable()
export class AppService {
constructor(private readonly httpService: HttpService) {}
async getUsers(): Promise<any> {
const response = await lastValueFrom(
this.httpService.get('https://api.example.com/users'),
);
console.log('Status:', response.status);
console.log('Status Text:', response.statusText);
console.log('Headers:', response.headers);
console.log('Data:', response.data);
return response.data;
}
}6. HTTP拦截器
HTTP拦截器允许我们在发送请求前或接收响应后修改请求或响应。我们可以使用拦截器来添加认证令牌、处理错误、添加日志等。
6.1 创建请求拦截器
// src/common/interceptors/request.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { HttpRequest } from '@nestjs/common/http';
@Injectable()
export class RequestInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest<HttpRequest>();
// 添加认证令牌
request.headers['Authorization'] = 'Bearer token123';
// 添加请求时间戳
request.headers['X-Request-Time'] = new Date().toISOString();
console.log('Request Interceptor:', request.url);
return next.handle();
}
}6.2 创建响应拦截器
// src/common/interceptors/response.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
return next.handle().pipe(
map((response) => {
const elapsed = Date.now() - now;
console.log(`Response Interceptor: ${elapsed}ms`);
// 可以在这里修改响应数据
return response;
}),
);
}
}6.3 在模块中使用拦截器
// src/app.module.ts
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { RequestInterceptor } from './common/interceptors/request.interceptor';
import { ResponseInterceptor } from './common/interceptors/response.interceptor';
@Module({
imports: [
HttpModule.register({
timeout: 5000,
maxRedirects: 5,
}),
],
controllers: [AppController],
providers: [
AppService,
{
provide: 'APP_INTERCEPTOR',
useClass: RequestInterceptor,
},
{
provide: 'APP_INTERCEPTOR',
useClass: ResponseInterceptor,
},
],
})
export class AppModule {}7. 错误处理
HTTP客户端可能会遇到以下类型的错误:
- 网络错误:如连接超时、网络不可用等
- HTTP错误:如404 Not Found、500 Internal Server Error等
- 响应解析错误:如响应数据格式不正确等
// src/app.service.ts
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { lastValueFrom, catchError } from 'rxjs';
import { AxiosError } from 'axios';
@Injectable()
export class AppService {
constructor(private readonly httpService: HttpService) {}
async getUsers(): Promise<any> {
try {
const response = await lastValueFrom(
this.httpService.get('https://api.example.com/users').pipe(
catchError((error: AxiosError) => {
console.error('HTTP Error:', error.message);
if (error.response) {
// 服务器返回错误状态码
console.error('Response Data:', error.response.data);
console.error('Response Status:', error.response.status);
console.error('Response Headers:', error.response.headers);
throw new HttpException(
error.response.data || 'Server error',
error.response.status,
);
} else if (error.request) {
// 请求已发送但没有收到响应
console.error('Request:', error.request);
throw new HttpException(
'No response received from server',
HttpStatus.GATEWAY_TIMEOUT,
);
} else {
// 请求配置错误
console.error('Request Error:', error.message);
throw new HttpException(
'Request configuration error',
HttpStatus.BAD_REQUEST,
);
}
}),
),
);
return response.data;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
}实用案例分析
案例1:调用外部API
需求分析
我们需要实现一个服务,调用外部天气API获取天气信息,并将结果返回给客户端。
实现方案
- 创建天气服务:
// src/weather/weather.service.ts
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { lastValueFrom, catchError } from 'rxjs';
import { AxiosError } from 'axios';
@Injectable()
export class WeatherService {
constructor(private readonly httpService: HttpService) {}
async getWeather(city: string): Promise<any> {
const apiKey = process.env.WEATHER_API_KEY;
const url = `https://api.weatherapi.com/v1/current.json`;
try {
const response = await lastValueFrom(
this.httpService.get(url, {
params: {
key: apiKey,
q: city,
},
}).pipe(
catchError((error: AxiosError) => {
console.error('Weather API Error:', error.message);
if (error.response) {
throw new HttpException(
error.response.data || 'Weather API error',
error.response.status,
);
} else {
throw new HttpException(
'Failed to connect to weather API',
HttpStatus.SERVICE_UNAVAILABLE,
);
}
}),
),
);
return response.data;
} catch (error) {
throw error;
}
}
}- 创建天气控制器:
// src/weather/weather.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { WeatherService } from './weather.service';
@Controller('weather')
export class WeatherController {
constructor(private readonly weatherService: WeatherService) {}
@Get()
async getWeather(@Query('city') city: string) {
return this.weatherService.getWeather(city);
}
}- 创建天气模块:
// src/weather/weather.module.ts
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { WeatherController } from './weather.controller';
import { WeatherService } from './weather.service';
@Module({
imports: [
HttpModule.register({
timeout: 10000,
maxRedirects: 5,
}),
],
controllers: [WeatherController],
providers: [WeatherService],
exports: [WeatherService],
})
export class WeatherModule {}案例2:实现API网关
需求分析
我们需要实现一个API网关,作为前端和后端微服务之间的中间层,转发请求到相应的微服务,并处理响应和错误。
实现方案
- 创建API网关服务:
// src/gateway/gateway.service.ts
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { lastValueFrom, catchError } from 'rxjs';
import { AxiosError } from 'axios';
@Injectable()
export class GatewayService {
constructor(private readonly httpService: HttpService) {}
async forwardRequest(method: string, service: string, endpoint: string, data?: any, params?: any): Promise<any> {
// 服务地址映射
const serviceUrls = {
users: 'http://users-service:3000',
products: 'http://products-service:3000',
orders: 'http://orders-service:3000',
};
const baseUrl = serviceUrls[service as keyof typeof serviceUrls];
if (!baseUrl) {
throw new HttpException('Service not found', HttpStatus.NOT_FOUND);
}
const url = `${baseUrl}${endpoint}`;
const config = {
params,
headers: {
'Content-Type': 'application/json',
},
};
try {
let response;
switch (method.toUpperCase()) {
case 'GET':
response = await lastValueFrom(
this.httpService.get(url, config).pipe(
catchError(this.handleError),
),
);
break;
case 'POST':
response = await lastValueFrom(
this.httpService.post(url, data, config).pipe(
catchError(this.handleError),
),
);
break;
case 'PUT':
response = await lastValueFrom(
this.httpService.put(url, data, config).pipe(
catchError(this.handleError),
),
);
break;
case 'DELETE':
response = await lastValueFrom(
this.httpService.delete(url, config).pipe(
catchError(this.handleError),
),
);
break;
default:
throw new HttpException('Invalid HTTP method', HttpStatus.BAD_REQUEST);
}
return response.data;
} catch (error) {
throw error;
}
}
private handleError = (error: AxiosError) => {
console.error('Gateway Error:', error.message);
if (error.response) {
throw new HttpException(
error.response.data || 'Service error',
error.response.status,
);
} else if (error.request) {
throw new HttpException(
'Service unavailable',
HttpStatus.SERVICE_UNAVAILABLE,
);
} else {
throw new HttpException(
'Request error',
HttpStatus.BAD_REQUEST,
);
}
};
}- 创建API网关控制器:
// src/gateway/gateway.controller.ts
import { Controller, All, Req, Res, Body, Query } from '@nestjs/common';
import { Request, Response } from 'express';
import { GatewayService } from './gateway.service';
@Controller('api')
export class GatewayController {
constructor(private readonly gatewayService: GatewayService) {}
@All('*')
async handleRequest(@Req() req: Request, @Res() res: Response, @Body() body: any, @Query() query: any) {
try {
// 从URL中提取服务名和端点
// 格式: /api/{service}/{endpoint}
const parts = req.url.split('/').filter(Boolean);
if (parts.length < 2) {
return res.status(400).json({ message: 'Invalid URL format' });
}
const service = parts[1];
const endpoint = '/' + parts.slice(2).join('/');
const result = await this.gatewayService.forwardRequest(
req.method,
service,
endpoint,
body,
query,
);
return res.json(result);
} catch (error) {
const statusCode = error.status || 500;
const message = error.message || 'Internal server error';
return res.status(statusCode).json({ message });
}
}
}- 创建API网关模块:
// src/gateway/gateway.module.ts
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { GatewayController } from './gateway.controller';
import { GatewayService } from './gateway.service';
@Module({
imports: [
HttpModule.register({
timeout: 30000,
maxRedirects: 5,
}),
],
controllers: [GatewayController],
providers: [GatewayService],
exports: [GatewayService],
})
export class GatewayModule {}常见问题与解决方案
1. 请求超时
可能原因:
- 网络延迟
- 外部服务响应慢
- 超时设置过短
解决方案:
- 增加超时时间
- 实现请求重试机制
- 优化外部服务性能
2. 认证失败
可能原因:
- 认证令牌过期
- 认证令牌格式错误
- 权限不足
解决方案:
- 实现令牌刷新机制
- 确保令牌格式正确
- 检查用户权限
3. CORS错误
可能原因:
- 跨域请求被拒绝
- CORS配置不当
解决方案:
- 在外部服务中配置正确的CORS策略
- 使用代理服务器
4. 响应数据格式错误
可能原因:
- 外部服务返回格式与预期不符
- 响应数据解析错误
解决方案:
- 增加数据验证
- 实现容错机制
- 与外部服务提供者协调数据格式
最佳实践
- 服务封装:将外部API调用封装到专门的服务中,提高代码可维护性
- 配置管理:将API地址、超时时间等配置放到配置文件中
- 错误处理:实现统一的错误处理机制,提高代码健壮性
- 日志记录:为HTTP请求和响应添加详细的日志记录
- 缓存策略:对频繁访问的数据使用缓存,减少HTTP请求
- 请求限流:实现请求限流,避免过度调用外部API
- 监控告警:监控HTTP请求的成功率和响应时间,及时发现问题
代码优化建议
- 使用装饰器:创建自定义装饰器简化HTTP请求的发送
- 实现重试机制:为失败的请求添加自动重试功能
- 使用拦截器:利用拦截器统一处理认证、日志等横切关注点
- 类型定义:为请求和响应数据添加TypeScript类型定义
- 测试覆盖:为HTTP客户端代码编写单元测试和集成测试
总结
NestJS的HTTP客户端模块提供了一种简洁、高效的方式来发送HTTP请求和处理响应。通过本文的学习,你应该已经掌握了:
- 如何安装和配置HTTP模块
- 如何发送不同类型的HTTP请求
- 如何配置请求选项和处理响应
- 如何使用HTTP拦截器
- 如何处理HTTP错误
- 如何实现外部API调用和API网关
HTTP客户端是现代应用程序的重要组成部分,它允许我们与外部服务进行通信,构建更加复杂和功能丰富的应用。合理使用NestJS的HTTP客户端功能,可以让你的应用程序更好地与外部世界集成。
互动问答
以下哪个是NestJS HTTP模块的正确安装命令?
A.npm install --save @nestjs/http
B.npm install --save @nestjs/axios
C.npm install --save http
D.npm install --save axios如何在NestJS中发送带有参数的GET请求?
如何使用HTTP拦截器添加认证令牌?
如何处理HTTP客户端的网络错误?
如何实现API网关来转发请求到微服务?