feat(platform-server): use absolute URLs from Location for HTTP (#37071)
Currently, requests from the server that do not use absolute URLs fail because the server does not have the same fallback mechanism that browser XHR does. This adds that mechanism by pulling the full URL out of the document.location object, if available. PR Close #37071
This commit is contained in:
parent
ce39755937
commit
9edea0bb75
|
@ -10,11 +10,14 @@
|
||||||
const xhr2: any = require('xhr2');
|
const xhr2: any = require('xhr2');
|
||||||
|
|
||||||
import {Injectable, Injector, Provider} from '@angular/core';
|
import {Injectable, Injector, Provider} from '@angular/core';
|
||||||
|
import {DOCUMENT} from '@angular/common';
|
||||||
import {HttpEvent, HttpRequest, HttpHandler, HttpBackend, XhrFactory, ɵHttpInterceptingHandler as HttpInterceptingHandler} from '@angular/common/http';
|
import {HttpEvent, HttpRequest, HttpHandler, HttpBackend, XhrFactory, ɵHttpInterceptingHandler as HttpInterceptingHandler} from '@angular/common/http';
|
||||||
|
|
||||||
import {Observable, Observer, Subscription} from 'rxjs';
|
import {Observable, Observer, Subscription} from 'rxjs';
|
||||||
|
|
||||||
|
// @see https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#URI-syntax
|
||||||
|
const isAbsoluteUrl = /^[a-zA-Z\-\+.]+:\/\//;
|
||||||
|
const FORWARD_SLASH = '/';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerXhr implements XhrFactory {
|
export class ServerXhr implements XhrFactory {
|
||||||
build(): XMLHttpRequest {
|
build(): XMLHttpRequest {
|
||||||
|
@ -102,11 +105,21 @@ 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) {
|
constructor(private backend: HttpBackend, private doc: Document) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
|
handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
|
||||||
|
const href = this.doc.location.href;
|
||||||
|
if (!isAbsoluteUrl.test(request.url) && href) {
|
||||||
|
const urlParts = Array.from(request.url);
|
||||||
|
if (request.url[0] === FORWARD_SLASH && href[href.length - 1] === FORWARD_SLASH) {
|
||||||
|
urlParts.shift();
|
||||||
|
} else if (request.url[0] !== FORWARD_SLASH && href[href.length - 1] !== FORWARD_SLASH) {
|
||||||
|
urlParts.splice(0, 0, FORWARD_SLASH);
|
||||||
|
}
|
||||||
|
return this.wrap(request.clone({url: href + urlParts.join('')}));
|
||||||
|
}
|
||||||
return this.wrap(request);
|
return this.wrap(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,12 +128,16 @@ export class ZoneClientBackend extends
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function zoneWrappedInterceptingHandler(backend: HttpBackend, injector: Injector) {
|
export function zoneWrappedInterceptingHandler(
|
||||||
|
backend: HttpBackend, injector: Injector, doc: Document) {
|
||||||
const realBackend: HttpBackend = new HttpInterceptingHandler(backend, injector);
|
const realBackend: HttpBackend = new HttpInterceptingHandler(backend, injector);
|
||||||
return new ZoneClientBackend(realBackend);
|
return new ZoneClientBackend(realBackend, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SERVER_HTTP_PROVIDERS: Provider[] = [
|
export const SERVER_HTTP_PROVIDERS: Provider[] = [
|
||||||
{provide: XhrFactory, useClass: ServerXhr},
|
{provide: XhrFactory, useClass: ServerXhr}, {
|
||||||
{provide: HttpHandler, useFactory: zoneWrappedInterceptingHandler, deps: [HttpBackend, Injector]}
|
provide: HttpHandler,
|
||||||
|
useFactory: zoneWrappedInterceptingHandler,
|
||||||
|
deps: [HttpBackend, Injector, DOCUMENT]
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
|
@ -793,6 +793,54 @@ describe('platform-server integration', () => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('can make relative HttpClient requests', async () => {
|
||||||
|
const platform = platformDynamicServer([
|
||||||
|
{provide: INITIAL_CONFIG, useValue: {document: '<app></app>', url: 'http://localhost'}}
|
||||||
|
]);
|
||||||
|
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/testing').flush('success!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can make relative HttpClient requests two slashes', async () => {
|
||||||
|
const platform = platformDynamicServer([
|
||||||
|
{provide: INITIAL_CONFIG, useValue: {document: '<app></app>', url: 'http://localhost/'}}
|
||||||
|
]);
|
||||||
|
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/testing').flush('success!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can make relative HttpClient requests no slashes', async () => {
|
||||||
|
const platform = platformDynamicServer([
|
||||||
|
{provide: INITIAL_CONFIG, useValue: {document: '<app></app>', url: 'http://localhost'}}
|
||||||
|
]);
|
||||||
|
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/testing').flush('success!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('requests are macrotasks', async(() => {
|
it('requests are macrotasks', async(() => {
|
||||||
const platform = platformDynamicServer(
|
const platform = platformDynamicServer(
|
||||||
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
||||||
|
|
Loading…
Reference in New Issue