-
Notifications
You must be signed in to change notification settings - Fork 27.2k
Expand file tree
/
Copy pathinterceptor.ts
More file actions
251 lines (234 loc) Β· 9.32 KB
/
interceptor.ts
File metadata and controls
251 lines (234 loc) Β· 9.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {
EnvironmentInjector,
inject,
InjectionToken,
runInInjectionContext,
PendingTasks,
} from '@angular/core';
import {Observable} from 'rxjs';
import {finalize} from 'rxjs/operators';
import type {HttpHandler} from './backend';
import {HttpRequest} from './request';
import {HttpEvent} from './response';
/**
* Intercepts and handles an `HttpRequest` or `HttpResponse`.
*
* Most interceptors transform the outgoing request before passing it to the
* next interceptor in the chain, by calling `next.handle(transformedReq)`.
* An interceptor may transform the
* response event stream as well, by applying additional RxJS operators on the stream
* returned by `next.handle()`.
*
* More rarely, an interceptor may handle the request entirely,
* and compose a new event stream instead of invoking `next.handle()`. This is an
* acceptable behavior, but keep in mind that further interceptors will be skipped entirely.
*
* It is also rare but valid for an interceptor to return multiple responses on the
* event stream for a single request.
*
* @publicApi
*
* @see [HTTP Guide](guide/http/interceptors)
* @see {@link HttpInterceptorFn}
*
* @usageNotes
*
* To use the same instance of `HttpInterceptors` for the entire app, import the `HttpClientModule`
* only in your `AppModule`, and add the interceptors to the root application injector.
* If you import `HttpClientModule` multiple times across different modules (for example, in lazy
* loading modules), each import creates a new copy of the `HttpClientModule`, which overwrites the
* interceptors provided in the root module.
*/
export interface HttpInterceptor {
/**
* Identifies and handles a given HTTP request.
* @param req The outgoing request object to handle.
* @param next The next interceptor in the chain, or the backend
* if no interceptors remain in the chain.
* @returns An observable of the event stream.
*/
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>;
}
/**
* Represents the next interceptor in an interceptor chain, or the real backend if there are no
* further interceptors.
*
* Most interceptors will delegate to this function, and either modify the outgoing request or the
* response when it arrives. Within the scope of the current request, however, this function may be
* called any number of times, for any number of downstream requests. Such downstream requests need
* not be to the same URL or even the same origin as the current request. It is also valid to not
* call the downstream handler at all, and process the current request entirely within the
* interceptor.
*
* This function should only be called within the scope of the request that's currently being
* intercepted. Once that request is complete, this downstream handler function should not be
* called.
*
* @publicApi
*
* @see [HTTP Guide](guide/http/interceptors)
*/
export type HttpHandlerFn = (req: HttpRequest<unknown>) => Observable<HttpEvent<unknown>>;
/**
* An interceptor for HTTP requests made via `HttpClient`.
*
* `HttpInterceptorFn`s are middleware functions which `HttpClient` calls when a request is made.
* These functions have the opportunity to modify the outgoing request or any response that comes
* back, as well as block, redirect, or otherwise change the request or response semantics.
*
* An `HttpHandlerFn` representing the next interceptor (or the backend which will make a real HTTP
* request) is provided. Most interceptors will delegate to this function, but that is not required
* (see `HttpHandlerFn` for more details).
*
* `HttpInterceptorFn`s are executed in an [injection context](guide/di/dependency-injection-context).
* They have access to `inject()` via the `EnvironmentInjector` from which they were configured.
*
* @see [HTTP Guide](guide/http/interceptors)
* @see {@link withInterceptors}
*
* @usageNotes
* Here is a noop interceptor that passes the request through without modifying it:
* ```ts
* export const noopInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next:
* HttpHandlerFn) => {
* return next(modifiedReq);
* };
* ```
*
* If you want to alter a request, clone it first and modify the clone before passing it to the
* `next()` handler function.
*
* Here is a basic interceptor that adds a bearer token to the headers
* ```ts
* export const authenticationInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next:
* HttpHandlerFn) => {
* const userToken = 'MY_TOKEN'; const modifiedReq = req.clone({
* headers: req.headers.set('Authorization', `Bearer ${userToken}`),
* });
*
* return next(modifiedReq);
* };
* ```
*/
export type HttpInterceptorFn = (
req: HttpRequest<unknown>,
next: HttpHandlerFn,
) => Observable<HttpEvent<unknown>>;
/**
* Function which invokes an HTTP interceptor chain.
*
* Each interceptor in the interceptor chain is turned into a `ChainedInterceptorFn` which closes
* over the rest of the chain (represented by another `ChainedInterceptorFn`). The last such
* function in the chain will instead delegate to the `finalHandlerFn`, which is passed down when
* the chain is invoked.
*
* This pattern allows for a chain of many interceptors to be composed and wrapped in a single
* `HttpInterceptorFn`, which is a useful abstraction for including different kinds of interceptors
* (e.g. legacy class-based interceptors) in the same chain.
*/
export type ChainedInterceptorFn<RequestT> = (
req: HttpRequest<RequestT>,
finalHandlerFn: HttpHandlerFn,
) => Observable<HttpEvent<RequestT>>;
export function interceptorChainEndFn(
req: HttpRequest<any>,
finalHandlerFn: HttpHandlerFn,
): Observable<HttpEvent<any>> {
return finalHandlerFn(req);
}
/**
* Constructs a `ChainedInterceptorFn` which adapts a legacy `HttpInterceptor` to the
* `ChainedInterceptorFn` interface.
*/
export function adaptLegacyInterceptorToChain(
chainTailFn: ChainedInterceptorFn<any>,
interceptor: HttpInterceptor,
): ChainedInterceptorFn<any> {
return (initialRequest, finalHandlerFn) =>
interceptor.intercept(initialRequest, {
handle: (downstreamRequest) => chainTailFn(downstreamRequest, finalHandlerFn),
});
}
/**
* Constructs a `ChainedInterceptorFn` which wraps and invokes a functional interceptor in the given
* injector.
*/
export function chainedInterceptorFn(
chainTailFn: ChainedInterceptorFn<unknown>,
interceptorFn: HttpInterceptorFn,
injector: EnvironmentInjector,
): ChainedInterceptorFn<unknown> {
return (initialRequest, finalHandlerFn) =>
runInInjectionContext(injector, () =>
interceptorFn(initialRequest, (downstreamRequest) =>
chainTailFn(downstreamRequest, finalHandlerFn),
),
);
}
/**
* A multi-provider token that represents the array of registered
* `HttpInterceptor` objects.
*
* @see [HTTP Guide](guide/http/interceptors)
*
* @publicApi
*/
export const HTTP_INTERCEPTORS = new InjectionToken<readonly HttpInterceptor[]>(
typeof ngDevMode !== 'undefined' && ngDevMode ? 'HTTP_INTERCEPTORS' : '',
);
/**
* A multi-provided token of `HttpInterceptorFn`s.
*/
export const HTTP_INTERCEPTOR_FNS = new InjectionToken<readonly HttpInterceptorFn[]>(
typeof ngDevMode !== 'undefined' && ngDevMode ? 'HTTP_INTERCEPTOR_FNS' : '',
{factory: () => []},
);
/**
* A multi-provided token of `HttpInterceptorFn`s that are only set in root.
*/
export const HTTP_ROOT_INTERCEPTOR_FNS = new InjectionToken<readonly HttpInterceptorFn[]>(
typeof ngDevMode !== 'undefined' && ngDevMode ? 'HTTP_ROOT_INTERCEPTOR_FNS' : '',
);
// TODO(atscott): We need a larger discussion about stability and what should contribute to stability.
// Should the whole interceptor chain contribute to stability or just the backend request #55075?
// Should HttpClient contribute to stability automatically at all?
export const REQUESTS_CONTRIBUTE_TO_STABILITY = new InjectionToken<boolean>(
typeof ngDevMode !== 'undefined' && ngDevMode ? 'REQUESTS_CONTRIBUTE_TO_STABILITY' : '',
// Providing a factory implies that the token is provided in root by default
{factory: () => true},
);
/**
* Creates an `HttpInterceptorFn` which lazily initializes an interceptor chain from the legacy
* class-based interceptors and runs the request through it.
*/
export function legacyInterceptorFnFactory(): HttpInterceptorFn {
let chain: ChainedInterceptorFn<any> | null = null;
return (req, handler) => {
if (chain === null) {
const interceptors = inject(HTTP_INTERCEPTORS, {optional: true}) ?? [];
// Note: interceptors are wrapped right-to-left so that final execution order is
// left-to-right. That is, if `interceptors` is the array `[a, b, c]`, we want to
// produce a chain that is conceptually `c(b(a(end)))`, which we build from the inside
// out.
chain = interceptors.reduceRight(
adaptLegacyInterceptorToChain,
interceptorChainEndFn as ChainedInterceptorFn<any>,
);
}
const pendingTasks = inject(PendingTasks);
const contributeToStability = inject(REQUESTS_CONTRIBUTE_TO_STABILITY);
if (contributeToStability) {
const removeTask = pendingTasks.add();
return chain(req, handler).pipe(finalize(removeTask));
} else {
return chain(req, handler);
}
};
}