2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @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-26 18:47:20 -04:00
|
|
|
import {Body} from './body';
|
|
|
|
import {ContentType, RequestMethod, ResponseContentType} from './enums';
|
2015-04-29 02:07:55 -04:00
|
|
|
import {Headers} from './headers';
|
2015-09-21 03:46:16 -04:00
|
|
|
import {normalizeMethodName} from './http_utils';
|
2016-06-08 19:38:52 -04:00
|
|
|
import {RequestArgs} from './interfaces';
|
|
|
|
import {URLSearchParams} from './url_search_params';
|
|
|
|
|
2015-04-29 02:07:55 -04:00
|
|
|
|
2015-06-09 18:18:57 -04:00
|
|
|
// TODO(jeffbcross): properly implement body accessors
|
|
|
|
/**
|
2015-06-24 03:27:07 -04:00
|
|
|
* Creates `Request` instances from provided values.
|
2015-06-09 18:18:57 -04:00
|
|
|
*
|
|
|
|
* The Request's interface is inspired by the Request constructor defined in the [Fetch
|
|
|
|
* Spec](https://fetch.spec.whatwg.org/#request-class),
|
|
|
|
* but is considered a static value whose body can be accessed many times. There are other
|
|
|
|
* differences in the implementation, but this is the most significant.
|
2015-09-17 19:35:49 -04:00
|
|
|
*
|
|
|
|
* `Request` instances are typically created by higher-level classes, like {@link Http} and
|
|
|
|
* {@link Jsonp}, but it may occasionally be useful to explicitly create `Request` instances.
|
|
|
|
* One such example is when creating services that wrap higher-level services, like {@link Http},
|
|
|
|
* where it may be useful to generate a `Request` with arbitrary headers and search params.
|
|
|
|
*
|
2015-09-24 04:32:43 -04:00
|
|
|
* ```typescript
|
2016-04-28 20:50:03 -04:00
|
|
|
* import {Injectable, Injector} from '@angular/core';
|
|
|
|
* import {HTTP_PROVIDERS, Http, Request, RequestMethod} from '@angular/http';
|
2015-09-17 19:35:49 -04:00
|
|
|
*
|
|
|
|
* @Injectable()
|
|
|
|
* class AutoAuthenticator {
|
|
|
|
* constructor(public http:Http) {}
|
|
|
|
* request(url:string) {
|
|
|
|
* return this.http.request(new Request({
|
2015-12-03 16:44:14 -05:00
|
|
|
* method: RequestMethod.Get,
|
2015-09-17 19:35:49 -04:00
|
|
|
* url: url,
|
|
|
|
* search: 'password=123'
|
|
|
|
* }));
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
*
|
2015-10-11 01:11:13 -04:00
|
|
|
* var injector = Injector.resolveAndCreate([HTTP_PROVIDERS, AutoAuthenticator]);
|
2015-09-17 19:35:49 -04:00
|
|
|
* var authenticator = injector.get(AutoAuthenticator);
|
2015-10-07 06:41:27 -04:00
|
|
|
* authenticator.request('people.json').subscribe(res => {
|
2015-09-17 19:35:49 -04:00
|
|
|
* //URL should have included '?password=123'
|
|
|
|
* console.log('people', res.json());
|
|
|
|
* });
|
|
|
|
* ```
|
2016-06-27 15:27:23 -04:00
|
|
|
*
|
|
|
|
* @experimental
|
2015-06-09 18:18:57 -04:00
|
|
|
*/
|
2016-05-26 18:47:20 -04:00
|
|
|
export class Request extends Body {
|
2015-06-09 18:18:57 -04:00
|
|
|
/**
|
|
|
|
* Http method with which to perform the request.
|
|
|
|
*/
|
2015-12-03 16:44:14 -05:00
|
|
|
method: RequestMethod;
|
2015-06-09 18:18:57 -04:00
|
|
|
/**
|
2015-09-17 19:35:49 -04:00
|
|
|
* {@link Headers} instance
|
2015-04-29 02:07:55 -04:00
|
|
|
*/
|
2015-06-09 18:18:57 -04:00
|
|
|
headers: Headers;
|
2015-06-19 15:14:12 -04:00
|
|
|
/** Url of the remote resource */
|
|
|
|
url: string;
|
2016-02-26 06:25:55 -05:00
|
|
|
/** Type of the request body **/
|
|
|
|
private contentType: ContentType;
|
2016-02-24 16:57:35 -05:00
|
|
|
/** Enable use credentials */
|
|
|
|
withCredentials: boolean;
|
2016-05-26 18:47:20 -04:00
|
|
|
/** Buffer to store the response */
|
|
|
|
responseType: ResponseContentType;
|
2015-10-10 12:37:29 -04:00
|
|
|
constructor(requestOptions: RequestArgs) {
|
2016-05-26 18:47:20 -04:00
|
|
|
super();
|
2015-06-24 03:27:07 -04:00
|
|
|
// TODO: assert that url is present
|
2016-11-12 08:08:58 -05:00
|
|
|
const url = requestOptions.url;
|
2017-04-17 14:12:53 -04:00
|
|
|
this.url = requestOptions.url !;
|
2017-04-27 18:41:46 -04:00
|
|
|
const paramsArg = requestOptions.params || requestOptions.search;
|
|
|
|
if (paramsArg) {
|
|
|
|
let params: string;
|
|
|
|
if (typeof paramsArg === 'object' && !(paramsArg instanceof URLSearchParams)) {
|
|
|
|
params = urlEncodeParams(paramsArg).toString();
|
|
|
|
} else {
|
|
|
|
params = paramsArg.toString();
|
|
|
|
}
|
2016-12-09 18:38:29 -05:00
|
|
|
if (params.length > 0) {
|
2015-07-13 14:47:10 -04:00
|
|
|
let prefix = '?';
|
2016-10-06 18:10:27 -04:00
|
|
|
if (this.url.indexOf('?') != -1) {
|
2015-07-13 14:47:10 -04:00
|
|
|
prefix = (this.url[this.url.length - 1] == '&') ? '' : '&';
|
|
|
|
}
|
|
|
|
// TODO: just delete search-query-looking string in url?
|
2016-12-09 18:38:29 -05:00
|
|
|
this.url = url + prefix + params;
|
2015-07-13 14:47:10 -04:00
|
|
|
}
|
|
|
|
}
|
2015-06-19 15:14:12 -04:00
|
|
|
this._body = requestOptions.body;
|
2017-04-17 14:12:53 -04:00
|
|
|
this.method = normalizeMethodName(requestOptions.method !);
|
2015-04-29 02:07:55 -04:00
|
|
|
// TODO(jeffbcross): implement behavior
|
|
|
|
// Defaults to 'omit', consistent with browser
|
2015-06-24 03:27:07 -04:00
|
|
|
this.headers = new Headers(requestOptions.headers);
|
2016-07-22 11:37:32 -04:00
|
|
|
this.contentType = this.detectContentType();
|
2017-04-17 14:12:53 -04:00
|
|
|
this.withCredentials = requestOptions.withCredentials !;
|
|
|
|
this.responseType = requestOptions.responseType !;
|
2016-02-26 06:25:55 -05:00
|
|
|
}
|
|
|
|
|
2016-07-22 11:37:32 -04:00
|
|
|
/**
|
|
|
|
* Returns the content type enum based on header options.
|
|
|
|
*/
|
|
|
|
detectContentType(): ContentType {
|
|
|
|
switch (this.headers.get('content-type')) {
|
|
|
|
case 'application/json':
|
|
|
|
return ContentType.JSON;
|
|
|
|
case 'application/x-www-form-urlencoded':
|
|
|
|
return ContentType.FORM;
|
|
|
|
case 'multipart/form-data':
|
|
|
|
return ContentType.FORM_DATA;
|
|
|
|
case 'text/plain':
|
|
|
|
case 'text/html':
|
|
|
|
return ContentType.TEXT;
|
|
|
|
case 'application/octet-stream':
|
2017-01-18 19:01:02 -05:00
|
|
|
return this._body instanceof ArrayBuffer ? ContentType.ARRAY_BUFFER : ContentType.BLOB;
|
2016-07-22 11:37:32 -04:00
|
|
|
default:
|
|
|
|
return this.detectContentTypeFromBody();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-26 06:25:55 -05:00
|
|
|
/**
|
|
|
|
* Returns the content type of request's body based on its type.
|
|
|
|
*/
|
2016-07-22 11:37:32 -04:00
|
|
|
detectContentTypeFromBody(): ContentType {
|
2016-02-26 06:25:55 -05:00
|
|
|
if (this._body == null) {
|
|
|
|
return ContentType.NONE;
|
|
|
|
} else if (this._body instanceof URLSearchParams) {
|
|
|
|
return ContentType.FORM;
|
|
|
|
} else if (this._body instanceof FormData) {
|
|
|
|
return ContentType.FORM_DATA;
|
|
|
|
} else if (this._body instanceof Blob) {
|
|
|
|
return ContentType.BLOB;
|
|
|
|
} else if (this._body instanceof ArrayBuffer) {
|
|
|
|
return ContentType.ARRAY_BUFFER;
|
2017-01-18 19:01:02 -05:00
|
|
|
} else if (this._body && typeof this._body === 'object') {
|
2016-02-26 06:25:55 -05:00
|
|
|
return ContentType.JSON;
|
|
|
|
} else {
|
|
|
|
return ContentType.TEXT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the request's body according to its type. If body is undefined, return
|
|
|
|
* null.
|
|
|
|
*/
|
|
|
|
getBody(): any {
|
|
|
|
switch (this.contentType) {
|
|
|
|
case ContentType.JSON:
|
2016-07-18 17:20:03 -04:00
|
|
|
return this.text();
|
2016-02-26 06:25:55 -05:00
|
|
|
case ContentType.FORM:
|
|
|
|
return this.text();
|
|
|
|
case ContentType.FORM_DATA:
|
|
|
|
return this._body;
|
|
|
|
case ContentType.TEXT:
|
|
|
|
return this.text();
|
|
|
|
case ContentType.BLOB:
|
|
|
|
return this.blob();
|
|
|
|
case ContentType.ARRAY_BUFFER:
|
|
|
|
return this.arrayBuffer();
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2015-04-29 02:07:55 -04:00
|
|
|
}
|
2016-02-26 06:25:55 -05:00
|
|
|
|
2017-04-27 18:41:46 -04:00
|
|
|
function urlEncodeParams(params: {[key: string]: any}): URLSearchParams {
|
|
|
|
const searchParams = new URLSearchParams();
|
|
|
|
Object.keys(params).forEach(key => {
|
|
|
|
const value = params[key];
|
|
|
|
if (value && Array.isArray(value)) {
|
|
|
|
value.forEach(element => searchParams.append(key, element.toString()));
|
|
|
|
} else {
|
|
|
|
searchParams.append(key, value.toString());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return searchParams;
|
|
|
|
}
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
const noop = function() {};
|
2016-02-26 06:25:55 -05:00
|
|
|
const w = typeof window == 'object' ? window : noop;
|
2016-06-08 18:45:15 -04:00
|
|
|
const FormData = (w as any /** TODO #9100 */)['FormData'] || noop;
|
|
|
|
const Blob = (w as any /** TODO #9100 */)['Blob'] || noop;
|
fix(http): introduce encodingHint for text() for better ArrayBuffer support
Currently, if a Response has an ArrayBuffer body and text() is called, Angular
attempts to convert the ArrayBuffer to a string. Doing this requires knowing
the encoding of the bytes in the buffer, which is context that we don't have.
Instead, we assume that the buffer is encoded in UTF-16, and attempt to process
it that way. Unfortunately the approach chosen (interpret buffer as Uint16Array and
create a Javascript string from each entry using String.fromCharCode) is incorrect
as it does not handle UTF-16 surrogate pairs. What Angular actually implements, then,
is UCS-2 decoding, which is equivalent to UTF-16 with characters restricted to the
base plane.
No standard way of decoding UTF-8 or UTF-16 exists in the browser today. APIs like
TextDecoder are only supported in a few browsers, and although hacks like using the
FileReader API with a Blob to force browsers to do content encoding detection and
decoding exist, they're slow and not compatible with the synchronous text() API.
Thus, this bug is fixed by introducing an encodingHint parameter to text(). The
default value of this parameter is 'legacy', indicating that the existing broken
behavior should be used - this prevents breaking existing apps. The only other
possible value of the hint is 'iso-8859' which interprets each byte of the buffer
with String.fromCharCode. UTF-8 and UTF-16 are not supported - it is up to the
consumer to get the ArrayBuffer and decode it themselves.
The parameter is a hint, as it's not always used (for example, if the conversion
to text doesn't involve an ArrayBuffer source). Additionally, this leaves the door
open for future implementations to perform more sophisticated encoding detection
and ignore the user-provided value if it can be proven to be incorrect.
Fixes #15932.
PR Close #16420
2017-04-28 14:54:40 -04:00
|
|
|
export const ArrayBuffer: ArrayBufferConstructor =
|
|
|
|
(w as any /** TODO #9100 */)['ArrayBuffer'] || noop;
|