2015-04-28 23:07:55 -07:00
|
|
|
import {ConnectionBackend, Connection} from '../interfaces';
|
2015-12-03 22:44:14 +01:00
|
|
|
import {ReadyState, RequestMethod, ResponseType} from '../enums';
|
2015-04-28 23:07:55 -07:00
|
|
|
import {Request} from '../static_request';
|
|
|
|
import {Response} from '../static_response';
|
2015-11-19 17:51:00 -08:00
|
|
|
import {Headers} from '../headers';
|
2016-04-28 17:50:03 -07:00
|
|
|
import {ResponseOptions} from '../base_response_options';
|
|
|
|
import {Injectable} from '@angular/core';
|
2015-06-24 00:27:07 -07:00
|
|
|
import {BrowserXhr} from './browser_xhr';
|
2016-05-05 14:25:44 -07:00
|
|
|
import {isPresent, isString} from '../../src/facade/lang';
|
2015-12-12 08:29:02 -08:00
|
|
|
import {Observable} from 'rxjs/Observable';
|
2016-02-01 17:05:50 -08:00
|
|
|
import {Observer} from 'rxjs/Observer';
|
2015-11-19 18:47:29 -08:00
|
|
|
import {isSuccess, getResponseURL} from '../http_utils';
|
2016-02-26 12:25:55 +01:00
|
|
|
import {ContentType} from '../enums';
|
2016-02-01 17:05:50 -08:00
|
|
|
|
2016-05-05 14:25:44 -07:00
|
|
|
const XSSI_PREFIX = ')]}\',\n';
|
|
|
|
|
2015-06-09 15:18:57 -07:00
|
|
|
/**
|
2016-05-05 12:46:07 -07:00
|
|
|
* Creates connections using `XMLHttpRequest`. Given a fully-qualified
|
|
|
|
* request, an `XHRConnection` will immediately create an `XMLHttpRequest` object and send the
|
|
|
|
* request.
|
|
|
|
*
|
|
|
|
* This class would typically not be created or interacted with directly inside applications, though
|
|
|
|
* the {@link MockConnection} may be interacted with in tests.
|
|
|
|
*/
|
2015-04-28 23:07:55 -07:00
|
|
|
export class XHRConnection implements Connection {
|
|
|
|
request: Request;
|
2015-06-09 15:18:57 -07:00
|
|
|
/**
|
2015-06-24 00:27:07 -07:00
|
|
|
* Response {@link EventEmitter} which emits a single {@link Response} value on load event of
|
|
|
|
* `XMLHttpRequest`.
|
2015-06-09 15:18:57 -07:00
|
|
|
*/
|
2015-10-30 23:52:37 -07:00
|
|
|
response: Observable<Response>;
|
2015-12-03 22:44:14 +01:00
|
|
|
readyState: ReadyState;
|
2015-06-24 00:27:07 -07:00
|
|
|
constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) {
|
2015-04-28 23:07:55 -07:00
|
|
|
this.request = req;
|
2016-04-13 11:25:45 -07:00
|
|
|
this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
|
2015-09-25 18:53:32 -04:00
|
|
|
let _xhr: XMLHttpRequest = browserXHR.build();
|
2015-12-03 22:44:14 +01:00
|
|
|
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
|
2016-05-05 14:25:44 -07:00
|
|
|
|
2015-09-25 18:53:32 -04:00
|
|
|
// load event handler
|
|
|
|
let onLoad = () => {
|
|
|
|
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
|
|
|
|
// response/responseType properties were introduced in XHR Level2 spec (supported by
|
|
|
|
// IE10)
|
2015-11-19 17:51:00 -08:00
|
|
|
let body = isPresent(_xhr.response) ? _xhr.response : _xhr.responseText;
|
2016-05-05 14:25:44 -07:00
|
|
|
// Implicitly strip a potential XSSI prefix.
|
|
|
|
if (isString(body) && body.startsWith(XSSI_PREFIX)) {
|
|
|
|
body = body.substring(XSSI_PREFIX.length);
|
|
|
|
}
|
2015-11-19 17:51:00 -08:00
|
|
|
let headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
|
2015-07-05 01:58:37 -07:00
|
|
|
|
2015-11-19 18:47:29 -08:00
|
|
|
let url = getResponseURL(_xhr);
|
|
|
|
|
2015-09-25 18:53:32 -04:00
|
|
|
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
|
2015-11-19 17:29:41 -08:00
|
|
|
let status: number = _xhr.status === 1223 ? 204 : _xhr.status;
|
2015-07-05 01:58:37 -07:00
|
|
|
|
2015-09-25 18:53:32 -04:00
|
|
|
// fix status code when it is 0 (0 status is undocumented).
|
|
|
|
// Occurs when accessing file resources or on Android 4.1 stock browser
|
|
|
|
// while retrieving files from application cache.
|
|
|
|
if (status === 0) {
|
2015-11-19 17:51:00 -08:00
|
|
|
status = body ? 200 : 0;
|
2015-09-25 18:53:32 -04:00
|
|
|
}
|
2015-09-13 19:31:56 +03:00
|
|
|
|
|
|
|
let statusText = _xhr.statusText || 'OK';
|
|
|
|
|
|
|
|
var responseOptions = new ResponseOptions({body, status, headers, statusText, url});
|
2015-09-25 18:53:32 -04:00
|
|
|
if (isPresent(baseResponseOptions)) {
|
|
|
|
responseOptions = baseResponseOptions.merge(responseOptions);
|
|
|
|
}
|
2015-11-19 17:29:41 -08:00
|
|
|
let response = new Response(responseOptions);
|
|
|
|
if (isSuccess(status)) {
|
|
|
|
responseObserver.next(response);
|
|
|
|
// TODO(gdi2290): defer complete if array buffer until done
|
|
|
|
responseObserver.complete();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
responseObserver.error(response);
|
2015-09-25 18:53:32 -04:00
|
|
|
};
|
|
|
|
// error event handler
|
2016-02-01 17:05:50 -08:00
|
|
|
let onError = (err: any) => {
|
2015-12-03 22:44:14 +01:00
|
|
|
var responseOptions = new ResponseOptions({body: err, type: ResponseType.Error});
|
2015-09-25 18:53:32 -04:00
|
|
|
if (isPresent(baseResponseOptions)) {
|
|
|
|
responseOptions = baseResponseOptions.merge(responseOptions);
|
|
|
|
}
|
|
|
|
responseObserver.error(new Response(responseOptions));
|
|
|
|
};
|
2015-07-05 01:58:37 -07:00
|
|
|
|
2016-02-26 12:25:55 +01:00
|
|
|
this.setDetectedContentType(req, _xhr);
|
|
|
|
|
2015-09-25 18:53:32 -04:00
|
|
|
if (isPresent(req.headers)) {
|
2015-10-08 16:01:18 -07:00
|
|
|
req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(',')));
|
2015-06-24 00:27:07 -07:00
|
|
|
}
|
|
|
|
|
2015-09-25 18:53:32 -04:00
|
|
|
_xhr.addEventListener('load', onLoad);
|
|
|
|
_xhr.addEventListener('error', onError);
|
2015-08-08 14:17:43 -07:00
|
|
|
|
2016-02-26 12:25:55 +01:00
|
|
|
_xhr.send(this.request.getBody());
|
2015-07-02 01:20:09 +03:00
|
|
|
|
2015-09-25 18:53:32 -04:00
|
|
|
return () => {
|
|
|
|
_xhr.removeEventListener('load', onLoad);
|
|
|
|
_xhr.removeEventListener('error', onError);
|
|
|
|
_xhr.abort();
|
|
|
|
};
|
|
|
|
});
|
2015-04-28 23:07:55 -07:00
|
|
|
}
|
2016-02-26 12:25:55 +01:00
|
|
|
|
|
|
|
setDetectedContentType(req, _xhr) {
|
|
|
|
// Skip if a custom Content-Type header is provided
|
|
|
|
if (isPresent(req.headers) && isPresent(req.headers['Content-Type'])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the detected content type
|
|
|
|
switch (req.contentType) {
|
|
|
|
case ContentType.NONE:
|
|
|
|
break;
|
|
|
|
case ContentType.JSON:
|
|
|
|
_xhr.setRequestHeader('Content-Type', 'application/json');
|
|
|
|
break;
|
|
|
|
case ContentType.FORM:
|
|
|
|
_xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
|
|
|
|
break;
|
|
|
|
case ContentType.TEXT:
|
|
|
|
_xhr.setRequestHeader('Content-Type', 'text/plain');
|
|
|
|
break;
|
|
|
|
case ContentType.BLOB:
|
|
|
|
var blob = req.blob();
|
|
|
|
if (blob.type) {
|
|
|
|
_xhr.setRequestHeader('Content-Type', blob.type);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-04-28 23:07:55 -07:00
|
|
|
}
|
|
|
|
|
2015-06-09 15:18:57 -07:00
|
|
|
/**
|
|
|
|
* Creates {@link XHRConnection} instances.
|
|
|
|
*
|
|
|
|
* This class would typically not be used by end users, but could be
|
|
|
|
* overridden if a different backend implementation should be used,
|
|
|
|
* such as in a node backend.
|
|
|
|
*
|
2015-10-19 15:37:32 +01:00
|
|
|
* ### Example
|
2015-06-09 15:18:57 -07:00
|
|
|
*
|
|
|
|
* ```
|
2016-04-28 17:50:03 -07:00
|
|
|
* import {Http, MyNodeBackend, HTTP_PROVIDERS, BaseRequestOptions} from '@angular/http';
|
2015-06-09 15:18:57 -07:00
|
|
|
* @Component({
|
2015-10-10 22:11:13 -07:00
|
|
|
* viewProviders: [
|
|
|
|
* HTTP_PROVIDERS,
|
2015-10-12 11:30:34 -07:00
|
|
|
* provide(Http, {useFactory: (backend, options) => {
|
2015-06-09 15:18:57 -07:00
|
|
|
* return new Http(backend, options);
|
2015-10-10 22:11:13 -07:00
|
|
|
* }, deps: [MyNodeBackend, BaseRequestOptions]})]
|
2015-06-09 15:18:57 -07:00
|
|
|
* })
|
|
|
|
* class MyComponent {
|
|
|
|
* constructor(http:Http) {
|
2015-10-08 17:21:12 -03:00
|
|
|
* http.request('people.json').subscribe(res => this.people = res.json());
|
2015-06-09 15:18:57 -07:00
|
|
|
* }
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
**/
|
2015-04-28 23:07:55 -07:00
|
|
|
@Injectable()
|
|
|
|
export class XHRBackend implements ConnectionBackend {
|
2015-06-24 00:27:07 -07:00
|
|
|
constructor(private _browserXHR: BrowserXhr, private _baseResponseOptions: ResponseOptions) {}
|
2015-04-28 23:07:55 -07:00
|
|
|
createConnection(request: Request): XHRConnection {
|
2015-06-24 00:27:07 -07:00
|
|
|
return new XHRConnection(request, this._browserXHR, this._baseResponseOptions);
|
2015-04-28 23:07:55 -07:00
|
|
|
}
|
|
|
|
}
|