fb475209a8
Review URL: https://codereview.chromium.org//54183007 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@29709 260f80e4-7a28-3924-810f-c04153c831b5
332 lines
10 KiB
Plaintext
332 lines
10 KiB
Plaintext
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
part of $LIBRARYNAME;
|
|
|
|
/**
|
|
* A client-side XHR request for getting data from a URL,
|
|
* formally known as XMLHttpRequest.
|
|
*
|
|
* HttpRequest can be used to obtain data from HTTP and FTP protocols,
|
|
* and is useful for AJAX-style page updates.
|
|
*
|
|
* The simplest way to get the contents of a text file, such as a
|
|
* JSON-formatted file, is with [getString].
|
|
* For example, the following code gets the contents of a JSON file
|
|
* and prints its length:
|
|
*
|
|
* var path = 'myData.json';
|
|
* HttpRequest.getString(path)
|
|
* .then((String fileContents) {
|
|
* print(fileContents.length);
|
|
* })
|
|
* .catchError((Error error) {
|
|
* print(error.toString());
|
|
* });
|
|
*
|
|
* ## Fetching data from other servers
|
|
*
|
|
* For security reasons, browsers impose restrictions on requests
|
|
* made by embedded apps.
|
|
* With the default behavior of this class,
|
|
* the code making the request must be served from the same origin
|
|
* (domain name, port, and application layer protocol)
|
|
* as the requested resource.
|
|
* In the example above, the myData.json file must be co-located with the
|
|
* app that uses it.
|
|
* You might be able to
|
|
* [get around this restriction](http://www.dartlang.org/articles/json-web-service/#a-note-on-cors-and-httprequest)
|
|
* by using CORS headers or JSONP.
|
|
*
|
|
* ## Other resources
|
|
*
|
|
* * [Fetch Data Dynamically](https://www.dartlang.org/docs/tutorials/fetchdata/),
|
|
* a tutorial from _A Game of Darts_,
|
|
* shows two different ways to use HttpRequest to get a JSON file.
|
|
* * [Get Input from a Form](https://www.dartlang.org/docs/tutorials/forms/),
|
|
* another tutorial from _A Game of Darts_,
|
|
* shows using HttpRequest with a custom server.
|
|
* * [Dart article on using HttpRequests](http://www.dartlang.org/articles/json-web-service/#getting-data)
|
|
* * [JS XMLHttpRequest](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest)
|
|
* * [Using XMLHttpRequest](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest)
|
|
*/
|
|
$(ANNOTATIONS)$(CLASS_MODIFIERS)class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC {
|
|
|
|
/**
|
|
* Creates a GET request for the specified [url].
|
|
*
|
|
* The server response must be a `text/` mime type for this request to
|
|
* succeed.
|
|
*
|
|
* This is similar to [request] but specialized for HTTP GET requests which
|
|
* return text content.
|
|
*
|
|
* See also:
|
|
*
|
|
* * [request]
|
|
*/
|
|
static Future<String> getString(String url,
|
|
{bool withCredentials, void onProgress(ProgressEvent e)}) {
|
|
return request(url, withCredentials: withCredentials,
|
|
onProgress: onProgress).then((xhr) => xhr.responseText);
|
|
}
|
|
|
|
/**
|
|
* Makes a server POST request with the specified data encoded as form data.
|
|
*
|
|
* This is roughly the POST equivalent of getString. This method is similar
|
|
* to sending a FormData object with broader browser support but limited to
|
|
* String values.
|
|
*
|
|
* See also:
|
|
*
|
|
* * [request]
|
|
*/
|
|
static Future<HttpRequest> postFormData(String url, Map<String, String> data,
|
|
{bool withCredentials, String responseType,
|
|
Map<String, String> requestHeaders,
|
|
void onProgress(ProgressEvent e)}) {
|
|
|
|
var parts = [];
|
|
data.forEach((key, value) {
|
|
parts.add('${Uri.encodeQueryComponent(key)}='
|
|
'${Uri.encodeQueryComponent(value)}');
|
|
});
|
|
var formData = parts.join('&');
|
|
|
|
if (requestHeaders == null) {
|
|
requestHeaders = <String, String>{};
|
|
}
|
|
requestHeaders.putIfAbsent('Content-Type',
|
|
() => 'application/x-www-form-urlencoded; charset=UTF-8');
|
|
|
|
return request(url, method: 'POST', withCredentials: withCredentials,
|
|
responseType: responseType,
|
|
requestHeaders: requestHeaders, sendData: formData,
|
|
onProgress: onProgress);
|
|
}
|
|
|
|
/**
|
|
* Creates and sends a URL request for the specified [url].
|
|
*
|
|
* By default `request` will perform an HTTP GET request, but a different
|
|
* method (`POST`, `PUT`, `DELETE`, etc) can be used by specifying the
|
|
* [method] parameter.
|
|
*
|
|
* The Future is completed when the response is available.
|
|
*
|
|
* If specified, `sendData` will send data in the form of a [ByteBuffer],
|
|
* [Blob], [Document], [String], or [FormData] along with the HttpRequest.
|
|
*
|
|
* The [withCredentials] parameter specified that credentials such as a cookie
|
|
* (already) set in the header or
|
|
* [authorization headers](http://tools.ietf.org/html/rfc1945#section-10.2)
|
|
* should be specified for the request. Details to keep in mind when using
|
|
* credentials:
|
|
*
|
|
* * Using credentials is only useful for cross-origin requests.
|
|
* * The `Access-Control-Allow-Origin` header of `url` cannot contain a wildcard (*).
|
|
* * The `Access-Control-Allow-Credentials` header of `url` must be set to true.
|
|
* * If `Access-Control-Expose-Headers` has not been set to true, only a subset of all the response headers will be returned when calling [getAllRequestHeaders].
|
|
*
|
|
* Note that requests for file:// URIs are only supported by Chrome extensions
|
|
* with appropriate permissions in their manifest. Requests to file:// URIs
|
|
* will also never fail- the Future will always complete successfully, even
|
|
* when the file cannot be found.
|
|
*
|
|
* See also: [authorization headers](http://en.wikipedia.org/wiki/Basic_access_authentication).
|
|
*/
|
|
static Future<HttpRequest> request(String url,
|
|
{String method, bool withCredentials, String responseType,
|
|
String mimeType, Map<String, String> requestHeaders, sendData,
|
|
void onProgress(ProgressEvent e)}) {
|
|
var completer = new Completer<HttpRequest>();
|
|
|
|
var xhr = new HttpRequest();
|
|
if (method == null) {
|
|
method = 'GET';
|
|
}
|
|
xhr.open(method, url, async: true);
|
|
|
|
if (withCredentials != null) {
|
|
xhr.withCredentials = withCredentials;
|
|
}
|
|
|
|
if (responseType != null) {
|
|
xhr.responseType = responseType;
|
|
}
|
|
|
|
if (mimeType != null) {
|
|
xhr.overrideMimeType(mimeType);
|
|
}
|
|
|
|
if (requestHeaders != null) {
|
|
requestHeaders.forEach((header, value) {
|
|
xhr.setRequestHeader(header, value);
|
|
});
|
|
}
|
|
|
|
if (onProgress != null) {
|
|
xhr.onProgress.listen(onProgress);
|
|
}
|
|
|
|
xhr.onLoad.listen((e) {
|
|
// Note: file:// URIs have status of 0.
|
|
if ((xhr.status >= 200 && xhr.status < 300) ||
|
|
xhr.status == 0 || xhr.status == 304) {
|
|
completer.complete(xhr);
|
|
} else {
|
|
completer.completeError(e);
|
|
}
|
|
});
|
|
|
|
xhr.onError.listen(completer.completeError);
|
|
|
|
if (sendData != null) {
|
|
xhr.send(sendData);
|
|
} else {
|
|
xhr.send();
|
|
}
|
|
|
|
return completer.future;
|
|
}
|
|
|
|
/**
|
|
* Checks to see if the Progress event is supported on the current platform.
|
|
*/
|
|
static bool get supportsProgressEvent {
|
|
$if DART2JS
|
|
var xhr = new HttpRequest();
|
|
return JS('bool', '("onprogress" in #)', xhr);
|
|
$else
|
|
return true;
|
|
$endif
|
|
}
|
|
|
|
/**
|
|
* Checks to see if the current platform supports making cross origin
|
|
* requests.
|
|
*
|
|
* Note that even if cross origin requests are supported, they still may fail
|
|
* if the destination server does not support CORS requests.
|
|
*/
|
|
static bool get supportsCrossOrigin {
|
|
$if DART2JS
|
|
var xhr = new HttpRequest();
|
|
return JS('bool', '("withCredentials" in #)', xhr);
|
|
$else
|
|
return true;
|
|
$endif
|
|
}
|
|
|
|
/**
|
|
* Checks to see if the LoadEnd event is supported on the current platform.
|
|
*/
|
|
static bool get supportsLoadEndEvent {
|
|
$if DART2JS
|
|
var xhr = new HttpRequest();
|
|
return JS('bool', '("onloadend" in #)', xhr);
|
|
$else
|
|
return true;
|
|
$endif
|
|
}
|
|
|
|
/**
|
|
* Checks to see if the overrideMimeType method is supported on the current
|
|
* platform.
|
|
*/
|
|
static bool get supportsOverrideMimeType {
|
|
$if DART2JS
|
|
var xhr = new HttpRequest();
|
|
return JS('bool', '("overrideMimeType" in #)', xhr);
|
|
$else
|
|
return true;
|
|
$endif
|
|
}
|
|
|
|
/**
|
|
* Makes a cross-origin request to the specified URL.
|
|
*
|
|
* This API provides a subset of [request] which works on IE9. If IE9
|
|
* cross-origin support is not required then [request] should be used instead.
|
|
*/
|
|
@Experimental()
|
|
static Future<String> requestCrossOrigin(String url,
|
|
{String method, String sendData}) {
|
|
if (supportsCrossOrigin) {
|
|
return request(url, method: method, sendData: sendData).then((xhr) {
|
|
return xhr.responseText;
|
|
});
|
|
}
|
|
$if DART2JS
|
|
var completer = new Completer<String>();
|
|
if (method == null) {
|
|
method = 'GET';
|
|
}
|
|
var xhr = JS('var', 'new XDomainRequest()');
|
|
JS('', '#.open(#, #)', xhr, method, url);
|
|
JS('', '#.onload = #', xhr, convertDartClosureToJS((e) {
|
|
var response = JS('String', '#.responseText', xhr);
|
|
completer.complete(response);
|
|
}, 1));
|
|
JS('', '#.onerror = #', xhr, convertDartClosureToJS((e) {
|
|
completer.completeError(e);
|
|
}, 1));
|
|
|
|
// IE9 RTM - XDomainRequest issued requests may abort if all event handlers
|
|
// not specified
|
|
// http://social.msdn.microsoft.com/Forums/ie/en-US/30ef3add-767c-4436-b8a9-f1ca19b4812e/ie9-rtm-xdomainrequest-issued-requests-may-abort-if-all-event-handlers-not-specified
|
|
JS('', '#.onprogress = {}', xhr);
|
|
JS('', '#.ontimeout = {}', xhr);
|
|
JS('', '#.timeout = Number.MAX_VALUE', xhr);
|
|
|
|
if (sendData != null) {
|
|
JS('', '#.send(#)', xhr, sendData);
|
|
} else {
|
|
JS('', '#.send()', xhr);
|
|
}
|
|
|
|
return completer.future;
|
|
$endif
|
|
}
|
|
|
|
/**
|
|
* Returns all response headers as a key-value map.
|
|
*
|
|
* Multiple values for the same header key can be combined into one,
|
|
* separated by a comma and a space.
|
|
*
|
|
* See: http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method
|
|
*/
|
|
Map<String, String> get responseHeaders {
|
|
// from Closure's goog.net.Xhrio.getResponseHeaders.
|
|
var headers = <String, String>{};
|
|
var headersString = this.getAllResponseHeaders();
|
|
if (headersString == null) {
|
|
return headers;
|
|
}
|
|
var headersList = headersString.split('\r\n');
|
|
for (var header in headersList) {
|
|
if (header.isEmpty) {
|
|
continue;
|
|
}
|
|
|
|
var splitIdx = header.indexOf(': ');
|
|
if (splitIdx == -1) {
|
|
continue;
|
|
}
|
|
var key = header.substring(0, splitIdx).toLowerCase();
|
|
var value = header.substring(splitIdx + 2);
|
|
if (headers.containsKey(key)) {
|
|
headers[key] = '${headers[key]}, $value';
|
|
} else {
|
|
headers[key] = value;
|
|
}
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
$!MEMBERS
|
|
}
|