Angular HTTP请求的拦截器实现与应用
Angular HTTP 请求拦截器基础概念
在 Angular 应用开发中,HTTP 请求是与后端服务器进行数据交互的重要方式。然而,在实际项目里,常常需要对所有的 HTTP 请求或响应执行一些通用操作,例如添加身份验证头、记录日志、错误处理等。这时候,Angular 的 HTTP 请求拦截器就派上用场了。
HTTP 拦截器是 Angular 提供的一种强大机制,它允许开发者在 HTTP 请求发送之前以及响应返回之后,对请求或响应进行拦截并执行自定义操作。拦截器本质上是一个类,实现了 HttpInterceptor
接口。这个接口定义了一个 intercept
方法,该方法接收 HttpRequest
和 HttpHandler
作为参数,并返回一个 Observable<HttpEvent<any>>
。
创建基本的 HTTP 拦截器
- 生成拦截器类 首先,使用 Angular CLI 来生成一个拦截器类。在项目根目录下执行以下命令:
ng generate interceptor my - intercept
这将在 src/app
目录下生成一个名为 my - intercept.ts
的文件,内容如下:
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class MyInterceptInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request);
}
}
-
理解
intercept
方法参数request
:类型为HttpRequest<any>
,它代表即将发送的 HTTP 请求。这个对象是不可变的,意味着如果需要对请求进行修改,需要通过clone
方法创建一个新的请求实例。next
:类型为HttpHandler
,它是一个处理请求并返回响应的函数。调用next.handle(request)
会将请求传递给下一个拦截器(如果存在),或者最终传递给 HTTP 后端处理。
-
简单示例:记录请求日志 我们可以在拦截器中记录每个请求的信息,例如请求 URL 和请求方法。修改
intercept
方法如下:
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class MyInterceptInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
console.log('Request URL:', request.url);
console.log('Request Method:', request.method);
return next.handle(request).pipe(
tap(() => { },
(error) => {
console.error('Request Error:', error);
})
);
}
}
在这个例子中,我们在请求发送前打印了请求的 URL 和方法。同时,通过 rxjs
的 tap
操作符,在响应返回后(无论是成功还是失败),如果出现错误,打印错误信息。
注册拦截器
要使拦截器生效,需要将其注册到 Angular 的依赖注入系统中。有两种常见的注册方式:
- 在
app.module.ts
中注册 在app.module.ts
的providers
数组中添加拦截器:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { MyInterceptInterceptor } from './my - intercept.interceptor';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MyInterceptInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
这里通过 provide: HTTP_INTERCEPTORS
来指定我们要注册的是 HTTP 拦截器。useClass
指向我们创建的拦截器类 MyInterceptInterceptor
。multi: true
表示可以有多个拦截器,它们会按照注册的顺序依次执行。
- 在
HttpClientModule
中注册 也可以在导入HttpClientModule
时注册拦截器,例如在app.module.ts
中:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { MyInterceptInterceptor } from './my - intercept.interceptor';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule.withInterceptors([MyInterceptInterceptor])
],
bootstrap: [AppComponent]
})
export class AppModule { }
这种方式更为简洁,直接在 HttpClientModule
的 withInterceptors
方法中传入拦截器数组。
拦截器应用场景 - 添加身份验证头
在许多实际应用中,需要对每个请求添加身份验证头,通常是 JWT(JSON Web Token)。假设我们从本地存储中获取 JWT 并添加到请求头中。
- 修改拦截器类
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const token = localStorage.getItem('token');
if (token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
return next.handle(request);
}
}
在这个拦截器中,首先从本地存储获取 token
。如果 token
存在,通过 request.clone
方法创建一个新的请求,并在新请求的头中添加 Authorization
字段,其值为 Bearer
加上 token
。
- 注册新拦截器
在
app.module.ts
的providers
中注册这个新的拦截器:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { AuthInterceptor } from './auth.interceptor';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
这样,所有的 HTTP 请求都会带上 Authorization
头,前提是本地存储中有 token
。
拦截器应用场景 - 错误处理
拦截器也非常适合统一处理 HTTP 错误。例如,当后端返回 401(未授权)错误时,我们可以自动将用户重定向到登录页面。
- 创建错误处理拦截器
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Router } from '@angular/router';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private router: Router) { }
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
tap(() => { },
(error) => {
if (error.status === 401) {
this.router.navigate(['/login']);
}
throw error;
})
);
}
}
在这个拦截器中,通过 tap
操作符监听响应的错误。如果错误状态码是 401,使用 Router
导航到登录页面,然后重新抛出错误,以便其他部分的应用可以继续处理该错误。
- 注册错误处理拦截器
同样在
app.module.ts
的providers
中注册:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { ErrorInterceptor } from './error.interceptor';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: ErrorInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
现在,当应用中任何 HTTP 请求返回 401 错误时,用户会被自动重定向到登录页面。
多个拦截器的执行顺序
当应用中有多个拦截器时,它们的执行顺序是按照在 providers
数组中注册的顺序来的。
-
请求阶段 假设我们有三个拦截器
InterceptorA
、InterceptorB
和InterceptorC
按照顺序注册。当一个 HTTP 请求发起时,InterceptorA
的intercept
方法首先被调用,它可以对请求进行修改,然后调用next.handle(request)
将请求传递给InterceptorB
。InterceptorB
同样可以处理请求并传递给InterceptorC
,最后InterceptorC
将请求传递给 HTTP 后端。 -
响应阶段 当后端返回响应时,响应会按照相反的顺序经过拦截器。即
InterceptorC
首先处理响应,然后是InterceptorB
,最后是InterceptorA
。每个拦截器都可以对响应进行处理,例如修改响应数据、处理错误等。
条件性拦截器
有时候,我们可能只希望在特定条件下应用拦截器。例如,只对特定 URL 前缀的请求添加身份验证头。
- 修改身份验证拦截器
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class ConditionalAuthInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const restrictedUrls = ['/api', '/admin'];
const shouldAddAuthHeader = restrictedUrls.some(url => request.url.startsWith(url));
if (shouldAddAuthHeader) {
const token = localStorage.getItem('token');
if (token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
}
return next.handle(request);
}
}
在这个拦截器中,定义了一个 restrictedUrls
数组,包含需要添加身份验证头的 URL 前缀。通过 some
方法检查请求的 URL 是否以这些前缀开头,如果是,则添加 Authorization
头。
- 注册条件性拦截器
在
app.module.ts
中注册:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { ConditionalAuthInterceptor } from './conditional - auth.interceptor';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: ConditionalAuthInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
这样,只有对以 /api
或 /admin
开头的 URL 的请求,才会添加身份验证头。
拦截器与响应式编程
由于拦截器的 intercept
方法返回一个 Observable<HttpEvent<any>>
,这使得我们可以充分利用 RxJS 的操作符来处理请求和响应。
- 合并多个请求 假设我们有一个场景,需要在发送某个请求前,先获取另一个数据。例如,在获取用户详细信息前,先获取用户的基本信息来确定是否有权限。
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable, forkJoin } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class CompositeRequestInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (request.url === '/user/details') {
const baseInfoRequest = new HttpRequest('GET', '/user/base - info');
return forkJoin([
next.handle(baseInfoRequest),
next.handle(request)
]).pipe(
map(([baseInfoResponse, detailsResponse]) => {
// 这里可以根据 baseInfoResponse 处理 detailsResponse
return detailsResponse;
})
);
}
return next.handle(request);
}
}
在这个拦截器中,当请求的 URL 是 /user/details
时,创建一个获取用户基本信息的请求 baseInfoRequest
。通过 forkJoin
将两个请求合并,在 map
操作符中可以根据基本信息的响应来处理详细信息的响应。
- 重试失败的请求 有时候,请求可能因为网络波动等原因失败,我们可以通过拦截器实现自动重试机制。
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable, retry } from 'rxjs';
@Injectable()
export class RetryInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
retry(3)
);
}
}
在这个拦截器中,使用 retry
操作符,当请求失败时,最多重试 3 次。
拦截器在跨域请求中的应用
在实际开发中,跨域请求是常见的问题。虽然通常可以在后端配置 CORS(跨域资源共享),但在前端也可以通过拦截器做一些辅助处理。
- 添加跨域请求头 某些情况下,后端可能需要特定的跨域请求头。我们可以通过拦截器添加这些头。
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class CorsInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
request = request.clone({
setHeaders: {
'X - Requested - With': 'XMLHttpRequest'
}
});
return next.handle(request);
}
}
在这个拦截器中,为请求添加了 X - Requested - With
头,这在一些跨域场景中可能是后端所需要的。
- 处理跨域预检请求 对于复杂的跨域请求(例如 PUT、DELETE 方法,或者包含自定义头的请求),浏览器会先发送一个预检请求(OPTIONS 方法)。我们可以通过拦截器来处理这个预检请求。
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class PreflightInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (request.method === 'OPTIONS') {
request = request.clone({
headers: request.headers.set('Access - Control - Allow - Origin', '*')
});
}
return next.handle(request);
}
}
在这个拦截器中,当请求方法是 OPTIONS
时,设置 Access - Control - Allow - Origin
头为 *
,以模拟后端的 CORS 响应(实际生产中应根据后端配置设置正确的源)。
拦截器的性能考虑
虽然拦截器非常强大,但在使用时也需要考虑性能问题。
-
避免不必要的操作 在拦截器的
intercept
方法中,应尽量避免执行复杂、耗时的操作。例如,不要在拦截器中进行大量的计算或者数据库查询(如果可能的话)。因为每个 HTTP 请求都会经过拦截器,如果拦截器执行缓慢,会影响整个应用的响应速度。 -
合理使用 RxJS 操作符 在使用 RxJS 操作符处理请求和响应时,要注意操作符的性能影响。例如,一些操作符可能会创建大量的中间 Observable 对象,增加内存开销。尽量使用简单、高效的操作符来完成所需的功能。
-
缓存数据 如果拦截器需要获取一些数据(例如身份验证的
token
),可以考虑缓存这些数据,而不是每次请求都重新获取。例如,将token
存储在本地存储中,并在拦截器中直接从本地存储读取,而不是每次都从服务器获取。
拦截器与 Angular 服务的结合使用
拦截器可以与 Angular 服务紧密结合,以实现更复杂的功能。
- 使用服务提供配置信息
假设我们有一个配置服务
ConfigService
,它提供一些与 HTTP 请求相关的配置,例如 API 前缀。拦截器可以使用这个服务来动态修改请求 URL。
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { ConfigService } from './config.service';
@Injectable()
export class ApiPrefixInterceptor implements HttpInterceptor {
constructor(private configService: ConfigService) { }
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const apiPrefix = this.configService.getApiPrefix();
if (!request.url.startsWith('http')) {
request = request.clone({
url: apiPrefix + request.url
});
}
return next.handle(request);
}
}
在这个拦截器中,通过构造函数注入 ConfigService
,并从该服务获取 API 前缀,然后将其添加到请求 URL 中。
- 通过服务处理业务逻辑 我们可以创建一个专门处理错误业务逻辑的服务,在错误拦截器中调用这个服务来处理不同类型的错误。
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { ErrorService } from './error.service';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private errorService: ErrorService) { }
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
tap(() => { },
(error) => {
this.errorService.handleError(error);
throw error;
})
);
}
}
在这个拦截器中,当出现错误时,调用 ErrorService
的 handleError
方法来处理错误,这样可以将错误处理逻辑集中在一个服务中,便于维护和扩展。
拦截器在微前端架构中的应用
在微前端架构中,多个前端应用可能共享一些 HTTP 拦截器的逻辑。
- 共享拦截器模块
可以将通用的拦截器封装成一个独立的模块,供各个微前端应用使用。例如,创建一个
shared - interceptors
模块,其中包含身份验证拦截器、错误处理拦截器等。
import { NgModule } from '@angular/core';
import { AuthInterceptor } from './auth.interceptor';
import { ErrorInterceptor } from './error.interceptor';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
@NgModule({
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: ErrorInterceptor,
multi: true
}
]
})
export class SharedInterceptorsModule { }
然后,各个微前端应用可以导入这个模块来使用其中的拦截器。
- 微前端特定拦截器 每个微前端应用也可以有自己特定的拦截器。例如,某个微前端应用需要对特定的 API 进行额外的处理,可以创建一个只在该微前端应用中使用的拦截器,并在该应用的模块中注册。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { MicroFrontendAppComponent } from './micro - frontend - app.component';
import { MicroFrontendSpecificInterceptor } from './micro - frontend - specific.interceptor';
@NgModule({
declarations: [MicroFrontendAppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MicroFrontendSpecificInterceptor,
multi: true
}
],
bootstrap: [MicroFrontendAppComponent]
})
export class MicroFrontendAppModule { }
这样,在微前端架构中,可以灵活地管理和复用 HTTP 拦截器逻辑。
通过以上对 Angular HTTP 请求拦截器的详细介绍,包括其基础概念、创建、注册、各种应用场景、与其他 Angular 特性的结合以及在不同架构中的应用等方面,相信开发者能够更好地利用拦截器来优化和扩展 Angular 应用的 HTTP 通信功能。在实际项目中,根据具体需求合理设计和使用拦截器,可以提高代码的可维护性、复用性以及应用的整体性能。