diff --git a/packages/platform-server/src/http.ts b/packages/platform-server/src/http.ts index 981490dc47..a00ce87140 100644 --- a/packages/platform-server/src/http.ts +++ b/packages/platform-server/src/http.ts @@ -10,11 +10,14 @@ const xhr2: any = require('xhr2'); 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 {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() export class ServerXhr implements XhrFactory { build(): XMLHttpRequest { @@ -102,11 +105,21 @@ export abstract class ZoneMacroTaskWrapper { export class ZoneClientBackend extends ZoneMacroTaskWrapper, HttpEvent> implements HttpBackend { - constructor(private backend: HttpBackend) { + constructor(private backend: HttpBackend, private doc: Document) { super(); } handle(request: HttpRequest): Observable> { + 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); } @@ -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); - return new ZoneClientBackend(realBackend); + return new ZoneClientBackend(realBackend, doc); } export const SERVER_HTTP_PROVIDERS: Provider[] = [ - {provide: XhrFactory, useClass: ServerXhr}, - {provide: HttpHandler, useFactory: zoneWrappedInterceptingHandler, deps: [HttpBackend, Injector]} + {provide: XhrFactory, useClass: ServerXhr}, { + provide: HttpHandler, + useFactory: zoneWrappedInterceptingHandler, + deps: [HttpBackend, Injector, DOCUMENT] + } ]; diff --git a/packages/platform-server/test/integration_spec.ts b/packages/platform-server/test/integration_spec.ts index d073987aff..c8fbdc7a19 100644 --- a/packages/platform-server/test/integration_spec.ts +++ b/packages/platform-server/test/integration_spec.ts @@ -793,6 +793,54 @@ describe('platform-server integration', () => { }); })); + it('can make relative HttpClient requests', async () => { + const platform = platformDynamicServer([ + {provide: INITIAL_CONFIG, useValue: {document: '', 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('/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: '', 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('/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: '', 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('testing').subscribe((body: string) => { + NgZone.assertInAngularZone(); + expect(body).toEqual('success!'); + }); + mock.expectOne('http://localhost/testing').flush('success!'); + }); + }); + it('requests are macrotasks', async(() => { const platform = platformDynamicServer( [{provide: INITIAL_CONFIG, useValue: {document: ''}}]);