fix(http): error on non-200 status codes

BREAKING CHANGE:

previously http would only error on network errors to match the fetch
specification. Now status codes less than 200 and greater than 299 will
cause Http's Observable to error.

Closes #5130.
This commit is contained in:
Rob Wormald 2015-11-19 17:29:41 -08:00
parent a35a93d0da
commit 201f189d0e
3 changed files with 67 additions and 13 deletions

View File

@ -7,6 +7,7 @@ import {Injectable} from 'angular2/angular2';
import {BrowserXhr} from './browser_xhr'; import {BrowserXhr} from './browser_xhr';
import {isPresent} from 'angular2/src/facade/lang'; import {isPresent} from 'angular2/src/facade/lang';
import {Observable} from 'angular2/angular2'; import {Observable} from 'angular2/angular2';
import {isSuccess} from '../http_utils';
/** /**
* Creates connections using `XMLHttpRequest`. Given a fully-qualified * Creates connections using `XMLHttpRequest`. Given a fully-qualified
* request, an `XHRConnection` will immediately create an `XMLHttpRequest` object and send the * request, an `XHRConnection` will immediately create an `XMLHttpRequest` object and send the
@ -33,24 +34,30 @@ export class XHRConnection implements Connection {
// responseText is the old-school way of retrieving response (supported by IE8 & 9) // responseText is the old-school way of retrieving response (supported by IE8 & 9)
// response/responseType properties were introduced in XHR Level2 spec (supported by // response/responseType properties were introduced in XHR Level2 spec (supported by
// IE10) // IE10)
let response = isPresent(_xhr.response) ? _xhr.response : _xhr.responseText; let xhrResponse = isPresent(_xhr.response) ? _xhr.response : _xhr.responseText;
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450) // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
let status = _xhr.status === 1223 ? 204 : _xhr.status; let status: number = _xhr.status === 1223 ? 204 : _xhr.status;
// fix status code when it is 0 (0 status is undocumented). // fix status code when it is 0 (0 status is undocumented).
// Occurs when accessing file resources or on Android 4.1 stock browser // Occurs when accessing file resources or on Android 4.1 stock browser
// while retrieving files from application cache. // while retrieving files from application cache.
if (status === 0) { if (status === 0) {
status = response ? 200 : 0; status = xhrResponse ? 200 : 0;
} }
var responseOptions = new ResponseOptions({body: response, status: status}); var responseOptions = new ResponseOptions({body: xhrResponse, status: status});
if (isPresent(baseResponseOptions)) { if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions); responseOptions = baseResponseOptions.merge(responseOptions);
} }
responseObserver.next(new Response(responseOptions)); let response = new Response(responseOptions);
if (isSuccess(status)) {
responseObserver.next(response);
// TODO(gdi2290): defer complete if array buffer until done // TODO(gdi2290): defer complete if array buffer until done
responseObserver.complete(); responseObserver.complete();
return;
}
responseObserver.error(response);
}; };
// error event handler // error event handler
let onError = (err) => { let onError = (err) => {

View File

@ -1,6 +1,7 @@
import {isString} from 'angular2/src/facade/lang'; import {isString} from 'angular2/src/facade/lang';
import {RequestMethods} from './enums'; import {RequestMethods} from './enums';
import {makeTypeError} from 'angular2/src/facade/exceptions'; import {makeTypeError} from 'angular2/src/facade/exceptions';
import {Response} from './static_response';
export function normalizeMethodName(method): RequestMethods { export function normalizeMethodName(method): RequestMethods {
if (isString(method)) { if (isString(method)) {
@ -14,4 +15,6 @@ export function normalizeMethodName(method): RequestMethods {
return method; return method;
} }
export const isSuccess = (status: number): boolean => (status >= 200 && status < 300);
export {isJsObject} from 'angular2/src/facade/lang'; export {isJsObject} from 'angular2/src/facade/lang';

View File

@ -99,6 +99,7 @@ export function main() {
expect(res.type).toBe(ResponseTypes.Error); expect(res.type).toBe(ResponseTypes.Error);
async.done(); async.done();
}); });
existingXHRs[0].setStatusCode(200);
existingXHRs[0].dispatchEvent('load'); existingXHRs[0].dispatchEvent('load');
})); }));
@ -107,7 +108,7 @@ export function main() {
new ResponseOptions({type: ResponseTypes.Error})); new ResponseOptions({type: ResponseTypes.Error}));
connection.response.subscribe(res => { expect(res.type).toBe(ResponseTypes.Error); }, connection.response.subscribe(res => { expect(res.type).toBe(ResponseTypes.Error); },
null, () => { async.done(); }); null, () => { async.done(); });
existingXHRs[0].setStatusCode(200);
existingXHRs[0].dispatchEvent('load'); existingXHRs[0].dispatchEvent('load');
})); }));
@ -164,8 +165,12 @@ export function main() {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(), var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode})); new ResponseOptions({status: statusCode}));
connection.response.subscribe(res => { connection.response.subscribe(
expect(res.status).toBe(statusCode); res => {
},
errRes => {
expect(errRes.status).toBe(statusCode);
async.done(); async.done();
}); });
@ -173,6 +178,44 @@ export function main() {
existingXHRs[0].dispatchEvent('load'); existingXHRs[0].dispatchEvent('load');
})); }));
it('should call next and complete on 200 codes', inject([AsyncTestCompleter], async => {
var nextCalled = false;
var errorCalled = false;
var statusCode = 200;
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode}));
connection.response.subscribe(
res => {
nextCalled = true;
expect(res.status).toBe(statusCode);
},
errRes => { errorCalled = true; }, () => {
expect(nextCalled).toBe(true);
expect(errorCalled).toBe(false);
async.done();
});
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
it('should call error and not complete on 300+ codes', inject([AsyncTestCompleter], async => {
var nextCalled = false;
var errorCalled = false;
var statusCode = 301;
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode}));
connection.response.subscribe(res => { nextCalled = true; }, errRes => {
expect(errRes.status).toBe(statusCode);
expect(nextCalled).toBe(false);
async.done();
}, () => { throw 'should not be called'; });
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
it('should normalize IE\'s 1223 status code into 204', inject([AsyncTestCompleter], async => { it('should normalize IE\'s 1223 status code into 204', inject([AsyncTestCompleter], async => {
var statusCode = 1223; var statusCode = 1223;
var normalizedCode = 204; var normalizedCode = 204;
@ -204,10 +247,11 @@ export function main() {
expect(ress.text()).toBe(responseBody); expect(ress.text()).toBe(responseBody);
async.done(); async.done();
}); });
existingXHRs[1].setStatusCode(200);
existingXHRs[1].setResponse(responseBody); existingXHRs[1].setResponse(responseBody);
existingXHRs[1].dispatchEvent('load'); existingXHRs[1].dispatchEvent('load');
}); });
existingXHRs[0].setStatusCode(200);
existingXHRs[0].setResponseText(responseBody); existingXHRs[0].setResponseText(responseBody);
existingXHRs[0].dispatchEvent('load'); existingXHRs[0].dispatchEvent('load');
})); }));