/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/common/http/src/backend.ts
153 строки
5 KB
Jaime Burgos
fix(http): run root interceptors in the terminal request chain
31 июл 2026, 18:32
Не верифицирован
31 июл 2026, 18:32
bb78286
Код
Авторство
О чём код?
/** * @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 { EnvironmentInjector, inject, Injectable, untracked, ɵConsole as Console, ɵformatRuntimeError as formatRuntimeError, PendingTasks, } from '@angular/core'; import {finalize} from 'rxjs/operators'; import {FetchBackend} from './fetch'; import {HttpRequest} from './request'; import {HttpEvent} from './response'; import {RuntimeErrorCode} from './errors'; import { ChainedInterceptorFn, chainedInterceptorFn, HTTP_INTERCEPTOR_FNS, HTTP_ROOT_INTERCEPTOR_FNS, interceptorChainEndFn, REQUESTS_CONTRIBUTE_TO_STABILITY, } 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 parentHandler = this.injector.get(HttpHandler, null, {skipSelf: true}); const isDelegating = parentHandler !== null && this.backend === parentHandler; const rootInterceptorFns = this.injector.get( HTTP_ROOT_INTERCEPTOR_FNS, [], isDelegating ? {self: true} : undefined, ); const dedupedInterceptorFns = Array.from( new Set([...this.injector.get(HTTP_INTERCEPTOR_FNS), ...rootInterceptorFns]), ); // 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>, ); } const chain = this.chain; if (this.contributeToStability) { const removeTask = this.pendingTasks.add(); return untracked(() => chain(initialRequest, (downstreamRequest) => this.backend.handle(downstreamRequest)), ).pipe(finalize(removeTask)); } else { return untracked(() => 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>>; }