fix(platform-server): avoid dependency cycle when using http interceptor (#24229)
Fixes #23023. When a HTTP Interceptor injects HttpClient it causes a DI cycle. This fix is to use Injector to lazily inject HTTP_INTERCEPTORS while setting up the HttpHandler on the server so as to break the cycle. PR Close #24229
This commit is contained in:
parent
68a799e950
commit
60aa943e2d
@ -11,7 +11,7 @@ export {HttpClient} from './src/client';
|
|||||||
export {HttpHeaders} from './src/headers';
|
export {HttpHeaders} from './src/headers';
|
||||||
export {HTTP_INTERCEPTORS, HttpInterceptor} from './src/interceptor';
|
export {HTTP_INTERCEPTORS, HttpInterceptor} from './src/interceptor';
|
||||||
export {JsonpClientBackend, JsonpInterceptor} from './src/jsonp';
|
export {JsonpClientBackend, JsonpInterceptor} from './src/jsonp';
|
||||||
export {HttpClientJsonpModule, HttpClientModule, HttpClientXsrfModule, interceptingHandler as ɵinterceptingHandler} from './src/module';
|
export {HttpClientJsonpModule, HttpClientModule, HttpClientXsrfModule, HttpInterceptingHandler as ɵHttpInterceptingHandler} from './src/module';
|
||||||
export {HttpParameterCodec, HttpParams, HttpUrlEncodingCodec} from './src/params';
|
export {HttpParameterCodec, HttpParams, HttpUrlEncodingCodec} from './src/params';
|
||||||
export {HttpRequest} from './src/request';
|
export {HttpRequest} from './src/request';
|
||||||
export {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpResponseBase, HttpSentEvent, HttpUserEvent} from './src/response';
|
export {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpResponseBase, HttpSentEvent, HttpUserEvent} from './src/response';
|
||||||
|
@ -42,23 +42,6 @@ export class HttpInterceptingHandler implements HttpHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an `HttpHandler` that applies a bunch of `HttpInterceptor`s
|
|
||||||
* to a request before passing it to the given `HttpBackend`.
|
|
||||||
*
|
|
||||||
* Meant to be used as a factory function within `HttpClientModule`.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export function interceptingHandler(
|
|
||||||
backend: HttpBackend, interceptors: HttpInterceptor[] | null = []): HttpHandler {
|
|
||||||
if (!interceptors) {
|
|
||||||
return backend;
|
|
||||||
}
|
|
||||||
return interceptors.reduceRight(
|
|
||||||
(next, interceptor) => new HttpInterceptorHandler(next, interceptor), backend);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory function that determines where to store JSONP callbacks.
|
* Factory function that determines where to store JSONP callbacks.
|
||||||
*
|
*
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
|
|
||||||
const xhr2: any = require('xhr2');
|
const xhr2: any = require('xhr2');
|
||||||
|
|
||||||
import {Injectable, Optional, Provider} from '@angular/core';
|
import {Injectable, Injector, Optional, Provider, InjectFlags} from '@angular/core';
|
||||||
import {BrowserXhr, Connection, ConnectionBackend, Http, ReadyState, Request, RequestOptions, Response, XHRBackend, XSRFStrategy} from '@angular/http';
|
import {BrowserXhr, Connection, ConnectionBackend, Http, ReadyState, Request, RequestOptions, Response, XHRBackend, XSRFStrategy} from '@angular/http';
|
||||||
|
|
||||||
import {HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HTTP_INTERCEPTORS, HttpBackend, XhrFactory, ɵinterceptingHandler as interceptingHandler} from '@angular/common/http';
|
import {HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HTTP_INTERCEPTORS, HttpBackend, XhrFactory, ɵHttpInterceptingHandler as HttpInterceptingHandler} from '@angular/common/http';
|
||||||
|
|
||||||
import {Observable, Observer, Subscription} from 'rxjs';
|
import {Observable, Observer, Subscription} from 'rxjs';
|
||||||
|
|
||||||
@ -156,9 +156,8 @@ export function httpFactory(xhrBackend: XHRBackend, options: RequestOptions) {
|
|||||||
return new Http(macroBackend, options);
|
return new Http(macroBackend, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function zoneWrappedInterceptingHandler(
|
export function zoneWrappedInterceptingHandler(backend: HttpBackend, injector: Injector) {
|
||||||
backend: HttpBackend, interceptors: HttpInterceptor[] | null) {
|
const realBackend: HttpBackend = new HttpInterceptingHandler(backend, injector);
|
||||||
const realBackend: HttpBackend = interceptingHandler(backend, interceptors);
|
|
||||||
return new ZoneClientBackend(realBackend);
|
return new ZoneClientBackend(realBackend);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +167,6 @@ export const SERVER_HTTP_PROVIDERS: Provider[] = [
|
|||||||
{provide: XhrFactory, useClass: ServerXhr}, {
|
{provide: XhrFactory, useClass: ServerXhr}, {
|
||||||
provide: HttpHandler,
|
provide: HttpHandler,
|
||||||
useFactory: zoneWrappedInterceptingHandler,
|
useFactory: zoneWrappedInterceptingHandler,
|
||||||
deps: [HttpBackend, [new Optional(), HTTP_INTERCEPTORS]]
|
deps: [HttpBackend, Injector]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -8,15 +8,16 @@
|
|||||||
|
|
||||||
import {AnimationBuilder, animate, style, transition, trigger} from '@angular/animations';
|
import {AnimationBuilder, animate, style, transition, trigger} from '@angular/animations';
|
||||||
import {APP_BASE_HREF, PlatformLocation, isPlatformServer} from '@angular/common';
|
import {APP_BASE_HREF, PlatformLocation, isPlatformServer} from '@angular/common';
|
||||||
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
|
||||||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
||||||
import {ApplicationRef, CompilerFactory, Component, HostListener, Inject, Input, NgModule, NgModuleRef, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation, destroyPlatform, getPlatform} from '@angular/core';
|
import {ApplicationRef, CompilerFactory, Component, HostListener, Inject, Injectable, Input, NgModule, NgModuleRef, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation, destroyPlatform, getPlatform} from '@angular/core';
|
||||||
import {TestBed, async, inject} from '@angular/core/testing';
|
import {TestBed, async, inject} from '@angular/core/testing';
|
||||||
import {Http, HttpModule, Response, ResponseOptions, XHRBackend} from '@angular/http';
|
import {Http, HttpModule, Response, ResponseOptions, XHRBackend} from '@angular/http';
|
||||||
import {MockBackend, MockConnection} from '@angular/http/testing';
|
import {MockBackend, MockConnection} from '@angular/http/testing';
|
||||||
import {BrowserModule, DOCUMENT, StateKey, Title, TransferState, makeStateKey} from '@angular/platform-browser';
|
import {BrowserModule, DOCUMENT, StateKey, Title, TransferState, makeStateKey} from '@angular/platform-browser';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, PlatformState, ServerModule, ServerTransferStateModule, platformDynamicServer, renderModule, renderModuleFactory} from '@angular/platform-server';
|
import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, PlatformState, ServerModule, ServerTransferStateModule, platformDynamicServer, renderModule, renderModuleFactory} from '@angular/platform-server';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
import {first} from 'rxjs/operators';
|
import {first} from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({selector: 'app', template: `Works!`})
|
@Component({selector: 'app', template: `Works!`})
|
||||||
@ -202,6 +203,26 @@ export class HttpAfterExampleModule {
|
|||||||
export class HttpClientExampleModule {
|
export class HttpClientExampleModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MyHttpInterceptor implements HttpInterceptor {
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||||
|
return next.handle(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
bootstrap: [MyServerApp],
|
||||||
|
declarations: [MyServerApp],
|
||||||
|
imports: [ServerModule, HttpClientModule, HttpClientTestingModule],
|
||||||
|
providers: [
|
||||||
|
{provide: HTTP_INTERCEPTORS, multi: true, useClass: MyHttpInterceptor},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class HttpInterceptorExampleModule {
|
||||||
|
}
|
||||||
|
|
||||||
@Component({selector: 'app', template: `<img [src]="'link'">`})
|
@Component({selector: 'app', template: `<img [src]="'link'">`})
|
||||||
class ImageApp {
|
class ImageApp {
|
||||||
}
|
}
|
||||||
@ -750,6 +771,21 @@ class EscapedTransferStoreModule {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
it('can use HttpInterceptor that injects HttpClient', () => {
|
||||||
|
const platform =
|
||||||
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
||||||
|
platform.bootstrapModule(HttpInterceptorExampleModule).then(ref => {
|
||||||
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
||||||
|
const http = ref.injector.get(HttpClient);
|
||||||
|
ref.injector.get<NgZone>(NgZone).run(() => {
|
||||||
|
http.get('http://localhost/testing').subscribe(body => {
|
||||||
|
NgZone.assertInAngularZone();
|
||||||
|
expect(body).toEqual('success!');
|
||||||
|
});
|
||||||
|
mock.expectOne('http://localhost/testing').flush('success!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ServerTransferStoreModule', () => {
|
describe('ServerTransferStoreModule', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user