Files
angular-docs-cn/modules/@angular/http/src/backends/xhr_backend.ts
T

233 lines
8.0 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
2016-05-31 16:44:13 -07:00
import {Injectable} from '@angular/core';
import {__platform_browser_private__} from '@angular/platform-browser';
2016-06-08 16:38:52 -07:00
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
2016-05-27 20:15:40 -07:00
2016-06-08 16:38:52 -07:00
import {ResponseOptions} from '../base_response_options';
import {ContentType, ReadyState, RequestMethod, ResponseContentType, ResponseType} from '../enums';
2016-10-19 13:42:39 -07:00
import {isPresent} from '../facade/lang';
2016-06-08 16:38:52 -07:00
import {Headers} from '../headers';
import {getResponseURL, isSuccess} from '../http_utils';
import {Connection, ConnectionBackend, XSRFStrategy} from '../interfaces';
2015-04-28 23:07:55 -07:00
import {Request} from '../static_request';
import {Response} from '../static_response';
2016-06-08 16:38:52 -07:00
import {BrowserXhr} from './browser_xhr';
2016-02-01 17:05:50 -08: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.
*
* @experimental
2016-05-05 12:46:07 -07:00
*/
2015-04-28 23:07:55 -07:00
export class XHRConnection implements Connection {
request: Request;
2015-06-09 15:18:57 -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
*/
response: Observable<Response>;
2015-12-03 22:44:14 +01:00
readyState: ReadyState;
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>) => {
let _xhr: XMLHttpRequest = browserXHR.build();
2015-12-03 22:44:14 +01:00
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
2016-02-24 22:57:35 +01:00
if (isPresent(req.withCredentials)) {
_xhr.withCredentials = req.withCredentials;
}
// load event handler
let onLoad = () => {
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
// response/responseType properties were introduced in ResourceLoader Level2 spec (supported
// by IE10)
let body = _xhr.response === undefined ? _xhr.responseText : _xhr.response;
// Implicitly strip a potential XSSI prefix.
2016-10-19 13:42:39 -07:00
if (typeof body === 'string') body = body.replace(XSSI_PREFIX, '');
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);
// 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
// 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;
}
let statusText = _xhr.statusText || 'OK';
var responseOptions = new ResponseOptions({body, status, headers, statusText, url});
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
2015-11-19 17:29:41 -08:00
let response = new Response(responseOptions);
2016-01-15 05:49:24 -05:00
response.ok = isSuccess(status);
if (response.ok) {
2015-11-19 17:29:41 -08:00
responseObserver.next(response);
// TODO(gdi2290): defer complete if array buffer until done
responseObserver.complete();
return;
}
responseObserver.error(response);
};
// error event handler
2016-02-01 17:05:50 -08:00
let onError = (err: any) => {
var responseOptions = new ResponseOptions({
body: err,
type: ResponseType.Error,
status: _xhr.status,
statusText: _xhr.statusText,
});
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
responseObserver.error(new Response(responseOptions));
};
2015-07-05 01:58:37 -07:00
this.setDetectedContentType(req, _xhr);
if (isPresent(req.headers)) {
req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(',')));
}
2016-02-24 16:37:18 +01:00
// Select the correct buffer type to store the response
if (isPresent(req.responseType) && isPresent(_xhr.responseType)) {
switch (req.responseType) {
case ResponseContentType.ArrayBuffer:
_xhr.responseType = 'arraybuffer';
2016-02-24 16:37:18 +01:00
break;
case ResponseContentType.Json:
_xhr.responseType = 'json';
2016-02-24 16:37:18 +01:00
break;
case ResponseContentType.Text:
_xhr.responseType = 'text';
2016-02-24 16:37:18 +01:00
break;
case ResponseContentType.Blob:
_xhr.responseType = 'blob';
break;
default:
throw new Error('The selected responseType is not supported');
2016-02-24 16:37:18 +01:00
}
}
2016-02-24 16:37:18 +01:00
_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);
_xhr.send(this.request.getBody());
2015-07-02 01:20:09 +03:00
return () => {
_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();
};
});
2015-04-28 23:07:55 -07:00
}
2016-06-08 15:45:15 -07:00
setDetectedContentType(req: any /** TODO #9100 */, _xhr: any /** TODO #9100 */) {
// Skip if a custom Content-Type header is provided
if (isPresent(req.headers) && isPresent(req.headers.get('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
}
2016-05-27 20:15:40 -07:00
/**
* `XSRFConfiguration` sets up Cross Site Request Forgery (XSRF) protection for the application
2016-07-06 14:34:27 -07:00
* using a cookie. See {@link https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)}
* for more information on XSRF.
2016-05-27 20:15:40 -07:00
*
* Applications can configure custom cookie and header names by binding an instance of this class
* with different `cookieName` and `headerName` values. See the main HTTP documentation for more
* details.
*
* @experimental
2016-05-27 20:15:40 -07:00
*/
export class CookieXSRFStrategy implements XSRFStrategy {
constructor(
private _cookieName: string = 'XSRF-TOKEN', private _headerName: string = 'X-XSRF-TOKEN') {}
configureRequest(req: Request) {
2016-05-31 16:44:13 -07:00
let xsrfToken = __platform_browser_private__.getDOM().getCookie(this._cookieName);
if (xsrfToken) {
2016-05-27 20:15:40 -07:00
req.headers.set(this._headerName, xsrfToken);
}
}
}
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.
*
* ### Example
2015-06-09 15:18:57 -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,
* {provide: Http, useFactory: (backend, options) => {
2015-06-09 15:18:57 -07:00
* return new Http(backend, options);
* }, deps: [MyNodeBackend, BaseRequestOptions]}]
2015-06-09 15:18:57 -07:00
* })
* class MyComponent {
* constructor(http:Http) {
* http.request('people.json').subscribe(res => this.people = res.json());
2015-06-09 15:18:57 -07:00
* }
* }
* ```
* @experimental
*/
2015-04-28 23:07:55 -07:00
@Injectable()
export class XHRBackend implements ConnectionBackend {
2016-05-27 20:15:40 -07:00
constructor(
private _browserXHR: BrowserXhr, private _baseResponseOptions: ResponseOptions,
private _xsrfStrategy: XSRFStrategy) {}
2015-04-28 23:07:55 -07:00
createConnection(request: Request): XHRConnection {
2016-05-27 20:15:40 -07:00
this._xsrfStrategy.configureRequest(request);
return new XHRConnection(request, this._browserXHR, this._baseResponseOptions);
2015-04-28 23:07:55 -07:00
}
}