From 46fc153f39db1729bff579719fdc7b73294e06f2 Mon Sep 17 00:00:00 2001 From: Rob Wormald Date: Thu, 19 Nov 2015 18:47:29 -0800 Subject: [PATCH] fix(http): return URL in Response Attach reponseURL or X-Request-URL to Response. Closes #5165 --- .../src/http/backends/jsonp_backend.ts | 4 +- .../angular2/src/http/backends/xhr_backend.ts | 6 ++- modules/angular2/src/http/http_utils.ts | 10 +++++ .../test/http/backends/xhr_backend_spec.ts | 38 +++++++++++++++++++ 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/modules/angular2/src/http/backends/jsonp_backend.ts b/modules/angular2/src/http/backends/jsonp_backend.ts index 12bad4c0ebe18..4059ce7c4a4fd 100644 --- a/modules/angular2/src/http/backends/jsonp_backend.ts +++ b/modules/angular2/src/http/backends/jsonp_backend.ts @@ -57,7 +57,7 @@ export class JSONPConnection_ extends JSONPConnection { _dom.cleanup(script); if (!this._finished) { let responseOptions = - new ResponseOptions({body: JSONP_ERR_NO_CALLBACK, type: ResponseTypes.Error}); + new ResponseOptions({body: JSONP_ERR_NO_CALLBACK, type: ResponseTypes.Error, url}); if (isPresent(baseResponseOptions)) { responseOptions = baseResponseOptions.merge(responseOptions); } @@ -65,7 +65,7 @@ export class JSONPConnection_ extends JSONPConnection { return; } - let responseOptions = new ResponseOptions({body: this._responseData}); + let responseOptions = new ResponseOptions({body: this._responseData, url}); if (isPresent(this.baseResponseOptions)) { responseOptions = this.baseResponseOptions.merge(responseOptions); } diff --git a/modules/angular2/src/http/backends/xhr_backend.ts b/modules/angular2/src/http/backends/xhr_backend.ts index 66bf375390fff..a234e07ac876e 100644 --- a/modules/angular2/src/http/backends/xhr_backend.ts +++ b/modules/angular2/src/http/backends/xhr_backend.ts @@ -8,7 +8,7 @@ import {Injectable} from 'angular2/angular2'; import {BrowserXhr} from './browser_xhr'; import {isPresent} from 'angular2/src/facade/lang'; import {Observable} from 'angular2/angular2'; -import {isSuccess} from '../http_utils'; +import {isSuccess, getResponseURL} from '../http_utils'; /** * Creates connections using `XMLHttpRequest`. Given a fully-qualified * request, an `XHRConnection` will immediately create an `XMLHttpRequest` object and send the @@ -39,6 +39,8 @@ export class XHRConnection implements Connection { let headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders()); + let url = getResponseURL(_xhr); + // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) let status: number = _xhr.status === 1223 ? 204 : _xhr.status; @@ -48,7 +50,7 @@ export class XHRConnection implements Connection { if (status === 0) { status = body ? 200 : 0; } - var responseOptions = new ResponseOptions({body, status, headers}); + var responseOptions = new ResponseOptions({body, status, headers, url}); if (isPresent(baseResponseOptions)) { responseOptions = baseResponseOptions.merge(responseOptions); } diff --git a/modules/angular2/src/http/http_utils.ts b/modules/angular2/src/http/http_utils.ts index 451469eaf4269..e4c2051e0bc9b 100644 --- a/modules/angular2/src/http/http_utils.ts +++ b/modules/angular2/src/http/http_utils.ts @@ -17,4 +17,14 @@ export function normalizeMethodName(method): RequestMethods { export const isSuccess = (status: number): boolean => (status >= 200 && status < 300); +export function getResponseURL(xhr: any): string { + if ('responseURL' in xhr) { + return xhr.responseURL; + } + if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { + return xhr.getResponseHeader('X-Request-URL'); + } + return; +} + export {isJsObject} from 'angular2/src/facade/lang'; diff --git a/modules/angular2/test/http/backends/xhr_backend_spec.ts b/modules/angular2/test/http/backends/xhr_backend_spec.ts index eac46518cf345..865eb8e4c1fcf 100644 --- a/modules/angular2/test/http/backends/xhr_backend_spec.ts +++ b/modules/angular2/test/http/backends/xhr_backend_spec.ts @@ -41,6 +41,7 @@ class MockBrowserXHR extends BrowserXhr { callbacks = new Map(); status: number; responseHeaders: string; + responseURL: string; constructor() { super(); var spy = new SpyObject(); @@ -56,10 +57,14 @@ class MockBrowserXHR extends BrowserXhr { setResponseText(value) { this.responseText = value; } + setResponseURL(value) { this.responseURL = value; } + setResponseHeaders(value) { this.responseHeaders = value; } getAllResponseHeaders() { return this.responseHeaders || ''; } + getResponseHeader(key) { return Headers.fromResponseHeaderString(this.responseHeaders).get(key); } + addEventListener(type: string, cb: Function) { this.callbacks.set(type, cb); } removeEventListener(type: string, cb: Function) { this.callbacks.delete(type); } @@ -285,6 +290,39 @@ export function main() { existingXHRs[0].setStatusCode(statusCode); existingXHRs[0].dispatchEvent('load'); })); + + it('should add the responseURL to the response', inject([AsyncTestCompleter], async => { + var statusCode = 200; + var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(), + new ResponseOptions({status: statusCode})); + + connection.response.subscribe(res => { + expect(res.url).toEqual('http://google.com'); + async.done(); + }); + + existingXHRs[0].setResponseURL('http://google.com'); + existingXHRs[0].setStatusCode(statusCode); + existingXHRs[0].dispatchEvent('load'); + })); + + it('should add use the X-Request-URL in CORS situations', + inject([AsyncTestCompleter], async => { + var statusCode = 200; + var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(), + new ResponseOptions({status: statusCode})); + var responseHeaders = `X-Request-URL: http://somedomain.com + Foo: Bar` + + connection.response.subscribe(res => { + expect(res.url).toEqual('http://somedomain.com'); + async.done(); + }); + + existingXHRs[0].setResponseHeaders(responseHeaders); + existingXHRs[0].setStatusCode(statusCode); + existingXHRs[0].dispatchEvent('load'); + })); }); }); }