fix(platform-server): throw a better error message for relative URLs (#15357)
Unlike in the browser, on the server there is no concept of a document origin. Thus, it is illegal to make requests for relative URLs against Http on platform-server. Currently this fails with a vague error: Error: Uncaught (in promise): Error at resolvePromise This change adds explicit validation and a friendlier error message: Error: URLs requested via Http on the server must be absolute. URL: /testing Another option considered was to track the concept of an origin for the platform and automatically prepend it to relative URLs. This would cause automatic "local RPCs" to be made, though, which would be an unexpected and undesirable default behavior. Fixes #15349 PR Close #15357
This commit is contained in:
parent
fbccd5cd38
commit
15a082c74e
|
@ -15,6 +15,14 @@ import {Observable} from 'rxjs/Observable';
|
||||||
import {Observer} from 'rxjs/Observer';
|
import {Observer} from 'rxjs/Observer';
|
||||||
import {Subscription} from 'rxjs/Subscription';
|
import {Subscription} from 'rxjs/Subscription';
|
||||||
|
|
||||||
|
const isAbsoluteUrl = /^[a-zA-Z\-\+.]+:\/\//;
|
||||||
|
|
||||||
|
function validateRequestUrl(url: string): void {
|
||||||
|
if (!isAbsoluteUrl.test(url)) {
|
||||||
|
throw new Error(`URLs requested via Http on the server must be absolute. URL: ${url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerXhr implements BrowserXhr {
|
export class ServerXhr implements BrowserXhr {
|
||||||
build(): XMLHttpRequest { return new xhr2.XMLHttpRequest(); }
|
build(): XMLHttpRequest { return new xhr2.XMLHttpRequest(); }
|
||||||
|
@ -30,6 +38,7 @@ export class ZoneMacroTaskConnection implements Connection {
|
||||||
lastConnection: Connection;
|
lastConnection: Connection;
|
||||||
|
|
||||||
constructor(public request: Request, backend: XHRBackend) {
|
constructor(public request: Request, backend: XHRBackend) {
|
||||||
|
validateRequestUrl(request.url);
|
||||||
this.response = new Observable((observer: Observer<Response>) => {
|
this.response = new Observable((observer: Observer<Response>) => {
|
||||||
let task: Task = null;
|
let task: Task = null;
|
||||||
let scheduled: boolean = false;
|
let scheduled: boolean = false;
|
||||||
|
|
|
@ -426,10 +426,10 @@ export function main() {
|
||||||
NgZone.assertInAngularZone();
|
NgZone.assertInAngularZone();
|
||||||
mock.connections.subscribe((mc: MockConnection) => {
|
mock.connections.subscribe((mc: MockConnection) => {
|
||||||
NgZone.assertInAngularZone();
|
NgZone.assertInAngularZone();
|
||||||
expect(mc.request.url).toBe('/testing');
|
expect(mc.request.url).toBe('http://localhost/testing');
|
||||||
mc.mockRespond(new Response(new ResponseOptions({body: 'success!', status: 200})));
|
mc.mockRespond(new Response(new ResponseOptions({body: 'success!', status: 200})));
|
||||||
});
|
});
|
||||||
http.get('/testing').subscribe(resp => {
|
http.get('http://localhost/testing').subscribe(resp => {
|
||||||
NgZone.assertInAngularZone();
|
NgZone.assertInAngularZone();
|
||||||
expect(resp.text()).toBe('success!');
|
expect(resp.text()).toBe('success!');
|
||||||
});
|
});
|
||||||
|
@ -449,7 +449,9 @@ export function main() {
|
||||||
expect(ref.injector.get(NgZone).hasPendingMacrotasks).toBeTruthy();
|
expect(ref.injector.get(NgZone).hasPendingMacrotasks).toBeTruthy();
|
||||||
mc.mockRespond(new Response(new ResponseOptions({body: 'success!', status: 200})));
|
mc.mockRespond(new Response(new ResponseOptions({body: 'success!', status: 200})));
|
||||||
});
|
});
|
||||||
http.get('/testing').subscribe(resp => { expect(resp.text()).toBe('success!'); });
|
http.get('http://localhost/testing').subscribe(resp => {
|
||||||
|
expect(resp.text()).toBe('success!');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -466,7 +468,9 @@ export function main() {
|
||||||
expect(ref.injector.get(NgZone).hasPendingMacrotasks).toBeTruthy();
|
expect(ref.injector.get(NgZone).hasPendingMacrotasks).toBeTruthy();
|
||||||
mc.mockRespond(new Response(new ResponseOptions({body: 'success!', status: 200})));
|
mc.mockRespond(new Response(new ResponseOptions({body: 'success!', status: 200})));
|
||||||
});
|
});
|
||||||
http.get('/testing').subscribe(resp => { expect(resp.text()).toBe('success!'); });
|
http.get('http://localhost/testing').subscribe(resp => {
|
||||||
|
expect(resp.text()).toBe('success!');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -483,10 +487,22 @@ export function main() {
|
||||||
expect(ref.injector.get(NgZone).hasPendingMacrotasks).toBeTruthy();
|
expect(ref.injector.get(NgZone).hasPendingMacrotasks).toBeTruthy();
|
||||||
mc.mockRespond(new Response(new ResponseOptions({body: 'success!', status: 200})));
|
mc.mockRespond(new Response(new ResponseOptions({body: 'success!', status: 200})));
|
||||||
});
|
});
|
||||||
http.get('/testing').subscribe(resp => { expect(resp.text()).toBe('success!'); });
|
http.get('http://localhost/testing').subscribe(resp => {
|
||||||
|
expect(resp.text()).toBe('success!');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
it('throws when given a relative URL', async(() => {
|
||||||
|
const platform = platformDynamicServer(
|
||||||
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
||||||
|
platform.bootstrapModule(ExampleModule).then(ref => {
|
||||||
|
const http = ref.injector.get(Http);
|
||||||
|
expect(() => http.get('/testing'))
|
||||||
|
.toThrowError(
|
||||||
|
'URLs requested via Http on the server must be absolute. URL: /testing');
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue