feat(platform-server): add option for absolute URL HTTP support (#37539)
In version 10.0.0-next.8, we introduced absolute URL support for server-based HTTP requests, so long as the fully-resolved URL was provided in the initial config. However, doing so represents a breaking change for users who already have their own interceptors to model this functionality, since our logic executes before all interceptors fire on a request. See original PR #37071. Therefore, we introduce a flag to make this change consistent with v9 behavior, allowing users to opt in to this new behavior. This commit also fixes two issues with the previous implementation: 1. if the server was initiated with a relative URL, the absolute URL construction would fail because needed components were empty 2. if the user's absolute URL was on a port, the port would not be included PR Close #37539
This commit is contained in:
parent
d12cdb5019
commit
d37049a2a2
@ -5,6 +5,7 @@ export declare const INITIAL_CONFIG: InjectionToken<PlatformConfig>;
|
|||||||
export declare interface PlatformConfig {
|
export declare interface PlatformConfig {
|
||||||
document?: string;
|
document?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
useAbsoluteUrl?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare const platformDynamicServer: (extraProviders?: StaticProvider[] | undefined) => PlatformRef;
|
export declare const platformDynamicServer: (extraProviders?: StaticProvider[] | undefined) => PlatformRef;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {INITIAL_CONFIG, PlatformConfig} from './tokens';
|
||||||
|
|
||||||
|
|
||||||
const xhr2: any = require('xhr2');
|
const xhr2: any = require('xhr2');
|
||||||
@ -104,15 +105,18 @@ export abstract class ZoneMacroTaskWrapper<S, R> {
|
|||||||
|
|
||||||
export class ZoneClientBackend extends
|
export class ZoneClientBackend extends
|
||||||
ZoneMacroTaskWrapper<HttpRequest<any>, HttpEvent<any>> implements HttpBackend {
|
ZoneMacroTaskWrapper<HttpRequest<any>, HttpEvent<any>> implements HttpBackend {
|
||||||
constructor(private backend: HttpBackend, private platformLocation: PlatformLocation) {
|
constructor(
|
||||||
|
private backend: HttpBackend, private platformLocation: PlatformLocation,
|
||||||
|
private config: PlatformConfig) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
|
handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
|
||||||
const {href, protocol, hostname} = this.platformLocation;
|
const {href, protocol, hostname, port} = this.platformLocation;
|
||||||
if (!isAbsoluteUrl.test(request.url) && href !== '/') {
|
if (this.config.useAbsoluteUrl && !isAbsoluteUrl.test(request.url) &&
|
||||||
|
isAbsoluteUrl.test(href)) {
|
||||||
const baseHref = this.platformLocation.getBaseHrefFromDOM() || href;
|
const baseHref = this.platformLocation.getBaseHrefFromDOM() || href;
|
||||||
const urlPrefix = `${protocol}//${hostname}`;
|
const urlPrefix = `${protocol}//${hostname}` + (port ? `:${port}` : '');
|
||||||
const baseUrl = new URL(baseHref, urlPrefix);
|
const baseUrl = new URL(baseHref, urlPrefix);
|
||||||
const url = new URL(request.url, baseUrl);
|
const url = new URL(request.url, baseUrl);
|
||||||
return this.wrap(request.clone({url: url.toString()}));
|
return this.wrap(request.clone({url: url.toString()}));
|
||||||
@ -126,15 +130,16 @@ export class ZoneClientBackend extends
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function zoneWrappedInterceptingHandler(
|
export function zoneWrappedInterceptingHandler(
|
||||||
backend: HttpBackend, injector: Injector, platformLocation: PlatformLocation) {
|
backend: HttpBackend, injector: Injector, platformLocation: PlatformLocation,
|
||||||
|
config: PlatformConfig) {
|
||||||
const realBackend: HttpBackend = new HttpInterceptingHandler(backend, injector);
|
const realBackend: HttpBackend = new HttpInterceptingHandler(backend, injector);
|
||||||
return new ZoneClientBackend(realBackend, platformLocation);
|
return new ZoneClientBackend(realBackend, platformLocation, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SERVER_HTTP_PROVIDERS: Provider[] = [
|
export const SERVER_HTTP_PROVIDERS: Provider[] = [
|
||||||
{provide: XhrFactory, useClass: ServerXhr}, {
|
{provide: XhrFactory, useClass: ServerXhr}, {
|
||||||
provide: HttpHandler,
|
provide: HttpHandler,
|
||||||
useFactory: zoneWrappedInterceptingHandler,
|
useFactory: zoneWrappedInterceptingHandler,
|
||||||
deps: [HttpBackend, Injector, PlatformLocation]
|
deps: [HttpBackend, Injector, PlatformLocation, INITIAL_CONFIG]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -14,8 +14,25 @@ import {InjectionToken} from '@angular/core';
|
|||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export interface PlatformConfig {
|
export interface PlatformConfig {
|
||||||
|
/**
|
||||||
|
* The initial DOM to use to bootstrap the server application.
|
||||||
|
* @default create a new DOM using Domino
|
||||||
|
*/
|
||||||
document?: string;
|
document?: string;
|
||||||
|
/**
|
||||||
|
* The URL for the current application state. This is
|
||||||
|
* used for initializing the platform's location and
|
||||||
|
* for setting absolute URL resolution for HTTP requests.
|
||||||
|
* @default none
|
||||||
|
*/
|
||||||
url?: string;
|
url?: string;
|
||||||
|
/**
|
||||||
|
* Whether to append the absolute URL to any relative HTTP
|
||||||
|
* requests. If set to true, this logic executes prior to
|
||||||
|
* any HTTP interceptors that may run later on in the request.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
useAbsoluteUrl?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -793,10 +793,12 @@ describe('platform-server integration', () => {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('can make relative HttpClient requests', async () => {
|
describe('relative requests', () => {
|
||||||
const platform = platformDynamicServer([
|
it('correctly maps to absolute URL request with base config', async () => {
|
||||||
{provide: INITIAL_CONFIG, useValue: {document: '<app></app>', url: 'http://localhost'}}
|
const platform = platformDynamicServer([{
|
||||||
]);
|
provide: INITIAL_CONFIG,
|
||||||
|
useValue: {document: '<app></app>', url: 'http://localhost', useAbsoluteUrl: true}
|
||||||
|
}]);
|
||||||
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
||||||
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
||||||
const http = ref.injector.get(HttpClient);
|
const http = ref.injector.get(HttpClient);
|
||||||
@ -809,10 +811,45 @@ describe('platform-server integration', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can make relative HttpClient requests two slashes', async () => {
|
it('uses default URL behavior when not enabled', async () => {
|
||||||
const platform = platformDynamicServer([
|
const platform = platformDynamicServer([{
|
||||||
{provide: INITIAL_CONFIG, useValue: {document: '<app></app>', url: 'http://localhost/'}}
|
provide: INITIAL_CONFIG,
|
||||||
]);
|
useValue: {document: '<app></app>', url: 'http://localhost', useAbsoluteUrl: false}
|
||||||
|
}]);
|
||||||
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
||||||
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
||||||
|
const http = ref.injector.get(HttpClient);
|
||||||
|
ref.injector.get(NgZone).run(() => {
|
||||||
|
http.get<string>('/testing').subscribe(() => {}, (body: string) => {
|
||||||
|
NgZone.assertInAngularZone();
|
||||||
|
expect(body).toEqual('error');
|
||||||
|
});
|
||||||
|
mock.expectOne('/testing').flush('error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly maps to absolute URL request with port', async () => {
|
||||||
|
const platform = platformDynamicServer([{
|
||||||
|
provide: INITIAL_CONFIG,
|
||||||
|
useValue: {document: '<app></app>', url: 'http://localhost:5000', useAbsoluteUrl: true}
|
||||||
|
}]);
|
||||||
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
||||||
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
||||||
|
const http = ref.injector.get(HttpClient);
|
||||||
|
ref.injector.get(NgZone).run(() => {
|
||||||
|
http.get<string>('/testing').subscribe((body: string) => {
|
||||||
|
NgZone.assertInAngularZone();
|
||||||
|
expect(body).toEqual('success!');
|
||||||
|
});
|
||||||
|
mock.expectOne('http://localhost:5000/testing').flush('success!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly maps to absolute URL request with two slashes', async () => {
|
||||||
|
const platform = platformDynamicServer([{
|
||||||
|
provide: INITIAL_CONFIG,
|
||||||
|
useValue: {document: '<app></app>', url: 'http://localhost/', useAbsoluteUrl: true}
|
||||||
|
}]);
|
||||||
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
||||||
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
||||||
const http = ref.injector.get(HttpClient);
|
const http = ref.injector.get(HttpClient);
|
||||||
@ -825,10 +862,11 @@ describe('platform-server integration', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can make relative HttpClient requests no slashes', async () => {
|
it('correctly maps to absolute URL request with no slashes', async () => {
|
||||||
const platform = platformDynamicServer([
|
const platform = platformDynamicServer([{
|
||||||
{provide: INITIAL_CONFIG, useValue: {document: '<app></app>', url: 'http://localhost'}}
|
provide: INITIAL_CONFIG,
|
||||||
]);
|
useValue: {document: '<app></app>', url: 'http://localhost', useAbsoluteUrl: true}
|
||||||
|
}]);
|
||||||
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
||||||
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
||||||
const http = ref.injector.get(HttpClient);
|
const http = ref.injector.get(HttpClient);
|
||||||
@ -841,10 +879,11 @@ describe('platform-server integration', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can make relative HttpClient requests no slashes longer url', async () => {
|
it('correctly maps to absolute URL request with longer url and no slashes', async () => {
|
||||||
const platform = platformDynamicServer([{
|
const platform = platformDynamicServer([{
|
||||||
provide: INITIAL_CONFIG,
|
provide: INITIAL_CONFIG,
|
||||||
useValue: {document: '<app></app>', url: 'http://localhost/path/page'}
|
useValue:
|
||||||
|
{document: '<app></app>', url: 'http://localhost/path/page', useAbsoluteUrl: true}
|
||||||
}]);
|
}]);
|
||||||
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
||||||
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
||||||
@ -858,10 +897,11 @@ describe('platform-server integration', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can make relative HttpClient requests slashes longer url', async () => {
|
it('correctly maps to absolute URL request with longer url and slashes', async () => {
|
||||||
const platform = platformDynamicServer([{
|
const platform = platformDynamicServer([{
|
||||||
provide: INITIAL_CONFIG,
|
provide: INITIAL_CONFIG,
|
||||||
useValue: {document: '<app></app>', url: 'http://localhost/path/page'}
|
useValue:
|
||||||
|
{document: '<app></app>', url: 'http://localhost/path/page', useAbsoluteUrl: true}
|
||||||
}]);
|
}]);
|
||||||
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
||||||
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
||||||
@ -875,11 +915,15 @@ describe('platform-server integration', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can make relative HttpClient requests slashes longer url with base href', async () => {
|
it('correctly maps to absolute URL request with longer url, slashes, and base href',
|
||||||
|
async () => {
|
||||||
const platform = platformDynamicServer([{
|
const platform = platformDynamicServer([{
|
||||||
provide: INITIAL_CONFIG,
|
provide: INITIAL_CONFIG,
|
||||||
useValue:
|
useValue: {
|
||||||
{document: '<base href="http://other"><app></app>', url: 'http://localhost/path/page'}
|
document: '<base href="http://other"><app></app>',
|
||||||
|
url: 'http://localhost/path/page',
|
||||||
|
useAbsoluteUrl: true
|
||||||
|
}
|
||||||
}]);
|
}]);
|
||||||
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
||||||
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
||||||
@ -892,6 +936,7 @@ describe('platform-server integration', () => {
|
|||||||
mock.expectOne('http://other/testing').flush('success!');
|
mock.expectOne('http://other/testing').flush('success!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('requests are macrotasks', async(() => {
|
it('requests are macrotasks', async(() => {
|
||||||
const platform = platformDynamicServer(
|
const platform = platformDynamicServer(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user