-
Notifications
You must be signed in to change notification settings - Fork 27.2k
Expand file tree
/
Copy pathbackend.ts
More file actions
148 lines (135 loc) · 5.15 KB
/
backend.ts
File metadata and controls
148 lines (135 loc) · 5.15 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
/**
* @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 {Observable} from 'rxjs';
import {HttpRequest} from './request';
import {HttpEvent} from './response';
import {FetchBackend} from './fetch';
import {HttpXhrBackend} from './xhr';
import {
EnvironmentInjector,
inject,
Injectable,
ɵConsole as Console,
ɵformatRuntimeError as formatRuntimeError,
PendingTasks,
} from '@angular/core';
import {finalize} from 'rxjs/operators';
import {RuntimeErrorCode} from './errors';
import {
ChainedInterceptorFn,
HTTP_INTERCEPTOR_FNS,
HTTP_ROOT_INTERCEPTOR_FNS,
REQUESTS_CONTRIBUTE_TO_STABILITY,
chainedInterceptorFn,
interceptorChainEndFn,
} from './interceptor';
/**
* A final `HttpHandler` which will dispatch the request via browser HTTP APIs to a backend.
*
* Interceptors sit between the `HttpClient` interface and the `HttpBackend`.
*
* When injected, `HttpBackend` dispatches requests directly to the backend, without going
* through the interceptor chain.
*
* @publicApi
*/
@Injectable({providedIn: 'root', useExisting: FetchBackend})
export abstract class HttpBackend implements HttpHandler {
abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
}
let fetchBackendWarningDisplayed = false;
/** Internal function to reset the flag in tests */
export function resetFetchBackendWarningFlag() {
fetchBackendWarningDisplayed = false;
}
@Injectable({providedIn: 'root'})
export class HttpInterceptorHandler implements HttpHandler {
private chain: ChainedInterceptorFn<unknown> | null = null;
private readonly pendingTasks = inject(PendingTasks);
private readonly contributeToStability = inject(REQUESTS_CONTRIBUTE_TO_STABILITY);
constructor(
private backend: HttpBackend,
private injector: EnvironmentInjector,
) {
// We strongly recommend using fetch backend for HTTP calls when SSR is used
// for an application. The logic below checks if that's the case and produces
// a warning otherwise.
if ((typeof ngDevMode === 'undefined' || ngDevMode) && !fetchBackendWarningDisplayed) {
// This flag is necessary because provideHttpClientTesting() overrides the backend
// even if `withFetch()` is used within the test. When the testing HTTP backend is provided,
// no HTTP calls are actually performed during the test, so producing a warning would be
// misleading.
const isTestingBackend = (this.backend as any).isTestingBackend;
if (
typeof ngServerMode !== 'undefined' &&
ngServerMode &&
!(this.backend instanceof FetchBackend) &&
!isTestingBackend
) {
fetchBackendWarningDisplayed = true;
injector
.get(Console)
.warn(
formatRuntimeError(
RuntimeErrorCode.NOT_USING_FETCH_BACKEND_IN_SSR,
'Angular detected that `HttpClient` is not configured ' +
"to use `fetch` APIs. It's strongly recommended to " +
'enable `fetch` for applications that use Server-Side Rendering ' +
'for better performance and compatibility. ' +
'To enable `fetch`, remove the `withXhr()` feature from the `provideHttpClient()` call',
),
);
}
}
}
handle(initialRequest: HttpRequest<any>): Observable<HttpEvent<any>> {
if (this.chain === null) {
const dedupedInterceptorFns = Array.from(
new Set([
...this.injector.get(HTTP_INTERCEPTOR_FNS),
...this.injector.get(HTTP_ROOT_INTERCEPTOR_FNS, []),
]),
);
// Note: interceptors are wrapped right-to-left so that final execution order is
// left-to-right. That is, if `dedupedInterceptorFns` 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.
this.chain = dedupedInterceptorFns.reduceRight(
(nextSequencedFn, interceptorFn) =>
chainedInterceptorFn(nextSequencedFn, interceptorFn, this.injector),
interceptorChainEndFn as ChainedInterceptorFn<unknown>,
);
}
if (this.contributeToStability) {
const removeTask = this.pendingTasks.add();
return this.chain(initialRequest, (downstreamRequest) =>
this.backend.handle(downstreamRequest),
).pipe(finalize(removeTask));
} else {
return this.chain(initialRequest, (downstreamRequest) =>
this.backend.handle(downstreamRequest),
);
}
}
}
/**
* Transforms an `HttpRequest` into a stream of `HttpEvent`s, one of which will likely be a
* `HttpResponse`.
*
* `HttpHandler` is injectable. When injected, the handler instance dispatches requests to the
* first interceptor in the chain, which dispatches to the second, etc, eventually reaching the
* `HttpBackend`.
*
* In an `HttpInterceptor`, the `HttpHandler` parameter is the next interceptor in the chain.
*
* @publicApi
*/
@Injectable({providedIn: 'root', useExisting: HttpInterceptorHandler})
export abstract class HttpHandler {
abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
}