Skip to content

Commit 9edea0b

Browse files
CaerusKarumhevery
authored andcommitted
feat(platform-server): use absolute URLs from Location for HTTP (angular#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 angular#37071
1 parent ce39755 commit 9edea0b

File tree

2 files changed

+72
-7
lines changed

2 files changed

+72
-7
lines changed

packages/platform-server/src/http.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010
const xhr2: any = require('xhr2');
1111

1212
import {Injectable, Injector, Provider} from '@angular/core';
13-
13+
import {DOCUMENT} from '@angular/common';
1414
import {HttpEvent, HttpRequest, HttpHandler, HttpBackend, XhrFactory, ɵHttpInterceptingHandler as HttpInterceptingHandler} from '@angular/common/http';
15-
1615
import {Observable, Observer, Subscription} from 'rxjs';
1716

17+
// @see https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#URI-syntax
18+
const isAbsoluteUrl = /^[a-zA-Z\-\+.]+:\/\//;
19+
const FORWARD_SLASH = '/';
20+
1821
@Injectable()
1922
export class ServerXhr implements XhrFactory {
2023
build(): XMLHttpRequest {
@@ -102,11 +105,21 @@ export abstract class ZoneMacroTaskWrapper<S, R> {
102105

103106
export class ZoneClientBackend extends
104107
ZoneMacroTaskWrapper<HttpRequest<any>, HttpEvent<any>> implements HttpBackend {
105-
constructor(private backend: HttpBackend) {
108+
constructor(private backend: HttpBackend, private doc: Document) {
106109
super();
107110
}
108111

109112
handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
113+
const href = this.doc.location.href;
114+
if (!isAbsoluteUrl.test(request.url) && href) {
115+
const urlParts = Array.from(request.url);
116+
if (request.url[0] === FORWARD_SLASH && href[href.length - 1] === FORWARD_SLASH) {
117+
urlParts.shift();
118+
} else if (request.url[0] !== FORWARD_SLASH && href[href.length - 1] !== FORWARD_SLASH) {
119+
urlParts.splice(0, 0, FORWARD_SLASH);
120+
}
121+
return this.wrap(request.clone({url: href + urlParts.join('')}));
122+
}
110123
return this.wrap(request);
111124
}
112125

@@ -115,12 +128,16 @@ export class ZoneClientBackend extends
115128
}
116129
}
117130

118-
export function zoneWrappedInterceptingHandler(backend: HttpBackend, injector: Injector) {
131+
export function zoneWrappedInterceptingHandler(
132+
backend: HttpBackend, injector: Injector, doc: Document) {
119133
const realBackend: HttpBackend = new HttpInterceptingHandler(backend, injector);
120-
return new ZoneClientBackend(realBackend);
134+
return new ZoneClientBackend(realBackend, doc);
121135
}
122136

123137
export const SERVER_HTTP_PROVIDERS: Provider[] = [
124-
{provide: XhrFactory, useClass: ServerXhr},
125-
{provide: HttpHandler, useFactory: zoneWrappedInterceptingHandler, deps: [HttpBackend, Injector]}
138+
{provide: XhrFactory, useClass: ServerXhr}, {
139+
provide: HttpHandler,
140+
useFactory: zoneWrappedInterceptingHandler,
141+
deps: [HttpBackend, Injector, DOCUMENT]
142+
}
126143
];

packages/platform-server/test/integration_spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,54 @@ describe('platform-server integration', () => {
793793
});
794794
}));
795795

796+
it('can make relative HttpClient requests', async () => {
797+
const platform = platformDynamicServer([
798+
{provide: INITIAL_CONFIG, useValue: {document: '<app></app>', url: 'http://localhost'}}
799+
]);
800+
const ref = await platform.bootstrapModule(HttpClientExampleModule);
801+
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
802+
const http = ref.injector.get(HttpClient);
803+
ref.injector.get(NgZone).run(() => {
804+
http.get<string>('/testing').subscribe((body: string) => {
805+
NgZone.assertInAngularZone();
806+
expect(body).toEqual('success!');
807+
});
808+
mock.expectOne('http://localhost/testing').flush('success!');
809+
});
810+
});
811+
812+
it('can make relative HttpClient requests two slashes', async () => {
813+
const platform = platformDynamicServer([
814+
{provide: INITIAL_CONFIG, useValue: {document: '<app></app>', url: 'http://localhost/'}}
815+
]);
816+
const ref = await platform.bootstrapModule(HttpClientExampleModule);
817+
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
818+
const http = ref.injector.get(HttpClient);
819+
ref.injector.get(NgZone).run(() => {
820+
http.get<string>('/testing').subscribe((body: string) => {
821+
NgZone.assertInAngularZone();
822+
expect(body).toEqual('success!');
823+
});
824+
mock.expectOne('http://localhost/testing').flush('success!');
825+
});
826+
});
827+
828+
it('can make relative HttpClient requests no slashes', async () => {
829+
const platform = platformDynamicServer([
830+
{provide: INITIAL_CONFIG, useValue: {document: '<app></app>', url: 'http://localhost'}}
831+
]);
832+
const ref = await platform.bootstrapModule(HttpClientExampleModule);
833+
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
834+
const http = ref.injector.get(HttpClient);
835+
ref.injector.get(NgZone).run(() => {
836+
http.get<string>('testing').subscribe((body: string) => {
837+
NgZone.assertInAngularZone();
838+
expect(body).toEqual('success!');
839+
});
840+
mock.expectOne('http://localhost/testing').flush('success!');
841+
});
842+
});
843+
796844
it('requests are macrotasks', async(() => {
797845
const platform = platformDynamicServer(
798846
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);

0 commit comments

Comments
 (0)