Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1695)

Unified Diff: third_party/pkg/angular/lib/core_dom/http.dart

Issue 124053002: Adding Angular and dependent packages for testing (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/pkg/angular/lib/core_dom/http.dart
diff --git a/third_party/pkg/angular/lib/core_dom/http.dart b/third_party/pkg/angular/lib/core_dom/http.dart
new file mode 100644
index 0000000000000000000000000000000000000000..2ac2d1380d0c7ddb992c4daceaa6119ac31f7f32
--- /dev/null
+++ b/third_party/pkg/angular/lib/core_dom/http.dart
@@ -0,0 +1,790 @@
+part of angular.core.dom;
+
+@NgInjectableService()
+class UrlRewriter {
+ String call(url) => url;
+}
+
+/**
+ * HTTP backend used by the [Http] service that delegates to dart:html's
+ * [HttpRequest] and deals with Dart bugs.
+ *
+ * Never use this service directly, instead use the higher-level [Http].
+ *
+ * During testing this implementation is swapped with [MockHttpBackend] which
+ * can be trained with responses.
+ */
+@NgInjectableService()
+class HttpBackend {
+ /**
+ * Wrapper around dart:html's [HttpRequest.request]
+ */
+ async.Future request(String url,
+ {String method, bool withCredentials, String responseType,
+ String mimeType, Map<String, String> requestHeaders, sendData,
+ void onProgress(dom.ProgressEvent e)}) {
+ // Complete inside a then to work-around dartbug.com/13051
+ var c = new async.Completer();
+
+ dom.HttpRequest.request(url,
+ method: method,
+ withCredentials: withCredentials,
+ responseType: responseType,
+ mimeType: mimeType,
+ requestHeaders: requestHeaders,
+ sendData: sendData,
+ onProgress: onProgress).then((x) => c.complete(x));
+ return c.future;
+ }
+}
+
+@NgInjectableService()
+class LocationWrapper {
+ get location => dom.window.location;
+}
+
+typedef RequestInterceptor(HttpResponseConfig);
+typedef RequestErrorInterceptor(dynamic);
+typedef Response(HttpResponse);
+typedef ResponseError(dynamic);
+
+/**
+* HttpInterceptors are used to modify the Http request. They can be added to
+* [HttpInterceptors] or passed into [Http.call].
+*/
+class HttpInterceptor {
+ RequestInterceptor request;
+ Response response;
+ RequestErrorInterceptor requestError;
+ ResponseError responseError;
+
+ /**
+ * All parameters are optional.
+ */
+ HttpInterceptor({
+ this.request, this.response,
+ this.requestError, this.responseError});
+}
+
+
+/**
+* The default transform data interceptor.abstract
+*
+* For requests, this interceptor will
+* automatically stringify any non-string non-file objects.
+*
+* For responses, this interceptor will unwrap JSON objects and
+* parse them into [Map]s.
+*/
+class DefaultTransformDataHttpInterceptor implements HttpInterceptor {
+ Function request = (HttpResponseConfig config) {
+ if (config.data != null && config.data is! String && config.data is! dom.File) {
+ config.data = JSON.encode(config.data);
+ }
+ return config;
+ };
+
+ static var _JSON_START = new RegExp(r'^\s*(\[|\{[^\{])');
+ static var _JSON_END = new RegExp(r'[\}\]]\s*$');
+ static var _PROTECTION_PREFIX = new RegExp('^\\)\\]\\}\',?\\n');
+ Function response = (HttpResponse r) {
+ if (r.data is String) {
+ var d = r.data;
+ d = d.replaceFirst(_PROTECTION_PREFIX, '');
+ if (d.contains(_JSON_START) && d.contains(_JSON_END)) {
+ d = JSON.decode(d);
+ }
+ return new HttpResponse.copy(r, data: d);
+ }
+ return r;
+ };
+
+ Function requestError, responseError;
+
+}
+
+/**
+ * A list of [HttpInterceptor]s.
+ */
+@NgInjectableService()
+class HttpInterceptors {
+ List<HttpInterceptor> _interceptors = [new DefaultTransformDataHttpInterceptor()];
+
+ add(HttpInterceptor x) => _interceptors.add(x);
+ addAll(List<HttpInterceptor> x) => _interceptors.addAll(x);
+
+ /**
+ * Called from [Http] to construct a [Future] chain.
+ */
+ constructChain(List chain) {
+ _interceptors.reversed.forEach((HttpInterceptor i) {
+ // AngularJS has an optimization of not including null interceptors.
+ chain.insert(0, [
+ i.request == null ? (x) => x : i.request,
+ i.requestError]);
+ chain.add([
+ i.response == null ? (x) => x : i.response,
+ i.responseError]);
+ });
+ }
+
+ /**
+ * Default constructor.
+ */
+ HttpInterceptors() {
+ _interceptors = [new DefaultTransformDataHttpInterceptor()];
+ }
+
+ /**
+ * Creates a [HttpInterceptors] from a [List]. Does not include the default interceptors.
+ */
+ HttpInterceptors.of([List interceptors]) {
+ _interceptors = interceptors;
+ }
+}
+
+/**
+ * The request configuration of the request associated with this response.
+ */
+class HttpResponseConfig {
+ /**
+ * The request's URL
+ */
+ String url;
+
+ /**
+ * The request params as a Map
+ */
+ Map params;
+
+ /**
+ * The header map without mangled keys
+ */
+ Map headers;
+
+ var data;
+
+
+ var _headersObj;
+
+ /**
+ * Header accessor. Given a string, it will return the matching header,
+ * case-insentivitively. Without a string, returns a header object will
+ * upper-case keys.
+ */
+ header([String name]) {
+ if (_headersObj == null) {
+ _headersObj = {};
+ headers.forEach((k,v) {
+ _headersObj[k.toLowerCase()] = v;
+ });
+ }
+
+ if (name != null) {
+ name = name.toLowerCase();
+ if (!_headersObj.containsKey(name)) return null;
+ return _headersObj[name];
+ }
+
+ return _headersObj;
+ }
+
+ /**
+ * Constructor
+ */
+ HttpResponseConfig({this.url, this.params, this.headers, this.data});
+}
+
+/**
+ * The response for an HTTP request. Returned from the [Http] service.
+ */
+class HttpResponse {
+ /**
+ * The HTTP status code.
+ */
+ int status;
+
+ /**
+ * DEPRECATED
+ */
+ var responseText;
+ Map _headers;
+
+ /**
+ * The [HttpResponseConfig] object which contains the requested URL
+ */
+ HttpResponseConfig config;
+
+ /**
+ * Constructor
+ */
+ HttpResponse([this.status, this.responseText, this._headers, this.config]);
+
+ /**
+ * Copy constructor. Creates a clone of the response, optionally with new
+ * data.
+ */
+ HttpResponse.copy(HttpResponse r, {data}) {
+ status = r.status;
+ responseText = data == null ? r.responseText : data;
+ _headers = r._headers == null ? null : new Map.from(r._headers);
+ config = r.config;
+ }
+
+ /**
+ * The response's data. Either a string or a transformed object.
+ */
+ get data => responseText;
+
+ /**
+ * The response's headers. Without parameters, this method will return the
+ * [Map] of headers. With [key] parameter, this method will return the specific
+ * header.
+ */
+ headers([String key]) {
+ if (key == null) {
+ return _headers;
+ }
+ if (_headers.containsKey(key)) {
+ return _headers[key];
+ }
+ return null;
+ }
+
+ /**
+ * Useful for debugging.
+ */
+ toString() => 'HTTP $status: $data';
+}
+
+/**
+ * Default header configuration.
+ */
+@NgInjectableService()
+class HttpDefaultHeaders {
+ static String _defaultContentType = 'application/json;charset=utf-8';
+ Map _headers = {
+ 'COMMON': {
+ 'Accept': 'application/json, text/plain, */*'
+ },
+ 'POST' : {
+ 'Content-Type': _defaultContentType
+ },
+ 'PUT' : {
+ 'Content-Type': _defaultContentType
+ },
+ 'PATCH' : {
+ 'Content-Type': _defaultContentType
+ }
+ };
+
+ _applyHeaders(method, ucHeaders, headers) {
+ if (!_headers.containsKey(method)) return;
+ _headers[method].forEach((k, v) {
+ if (!ucHeaders.contains(k.toUpperCase())) {
+ headers[k] = v;
+ }
+ });
+ }
+
+ /**
+ * Called from [Http], this method sets default headers on [headers]
+ */
+ setHeaders(Map<String, String> headers, String method) {
+ assert(headers != null);
+ var ucHeaders = headers.keys.map((x) => x.toUpperCase()).toSet();
+ _applyHeaders('COMMON', ucHeaders, headers);
+ _applyHeaders(method.toUpperCase(), ucHeaders, headers);
+ }
+
+ /**
+ * Returns the default header [Map] for a method. You can then modify
+ * the map.
+ *
+ * Passing 'common' as [method] will return a Map that contains headers
+ * common to all operations.
+ */
+ operator[](method) {
+ return _headers[method.toUpperCase()];
+ }
+}
+
+/**
+* Injected into the [Http] service. This class contains application-wide
+* HTTP defaults.
+*
+* The default implementation provides headers which the
+* Angular team believes to be useful.
+*/
+@NgInjectableService()
+class HttpDefaults {
+ /**
+ * The [HttpDefaultHeaders] object used by [Http] to add default headers
+ * to requests.
+ */
+ HttpDefaultHeaders headers;
+
+ /**
+ * The default cache. To enable caching application-wide, instantiate with a
+ * [Cache] object.
+ */
+ var cache;
+
+ /**
+ * The default XSRF cookie name. May not be null.
+ */
+ String xsrfCookieName = 'XSRF-TOKEN';
+
+ /**
+ * The default XSRF header name sent with the request. May not be null.
+ */
+ String xsrfHeaderName = 'X-XSRF-TOKEN';
+
+ /**
+ * Constructor intended for DI.
+ */
+ HttpDefaults(this.headers);
+}
+
+/**
+ * The [Http] service facilitates communication with the remote HTTP servers. It
+ * uses dart:html's [HttpRequest] and provides a number of features on top
+ * of the core Dart library.
+ *
+ * For unit testing, applications should use the [MockHttpBackend] service.
+ *
+ * # General usage
+ * The [call] method takes a number of named parameters and returns a
+ * [Future<HttpResponse>].
+ *
+ * http(method: 'GET', url: '/someUrl')
+ * .then((HttpResponse response) { .. },
+ * onError: (HttpRequest request) { .. });
+ *
+ * A response status code between 200 and 299 is considered a success status and
+ * will result in the 'then' being called. Note that if the response is a redirect,
+ * Dart's [HttpRequest] will transparently follow it, meaning that the error callback will not be
+ * called for such responses.
+ *
+ * # Shortcut methods
+ *
+ * The Http service also defines a number of shortcuts:
+ *
+ * http.get('/someUrl') is the same as http(method: 'GET', url: '/someUrl')
+ *
+ * See the method definitions below.
+ *
+ * # Setting HTTP Headers
+ *
+ * The [Http] service will add certain HTTP headers to requests. These defaults
+ * can be configured using the [HttpDefaultHeaders] object. The defaults are:
+ *
+ * - For all requests: `Accept: application/json, text/plain, * / *`
+ * - For POST, PUT, PATCH requests: `Content-Type: application/json`
+ *
+ * # Caching
+ *
+ * To enable caching, pass a [Cache] object into the [call] method. The [Http]
+ * service will store responses in the cache and return the response for
+ * any matching requests.
+ *
+ * Note that data is returned through a [Future], regardless of whether it
+ * came from the [Cache] or the server.
+ *
+ * If there are multiple GET requests for the same not-yet-in-cache URL
+ * while a cache is in use, only one request to the server will be made.
+ *
+ * # Interceptors
+ *
+ * Http uses the interceptors from [HttpInterceptors]. You can also include
+ * interceptors in the [call] method.
+ *
+ * # Security Considerations
+ *
+ * NOTE: < not yet documented >
+ */
+@NgInjectableService()
+class Http {
+ Map<String, async.Future<HttpResponse>> _pendingRequests = <String, async.Future<HttpResponse>>{};
+ BrowserCookies _cookies;
+ LocationWrapper _location;
+ UrlRewriter _rewriter;
+ HttpBackend _backend;
+ HttpInterceptors _interceptors;
+
+ /**
+ * The defaults for [Http]
+ */
+ HttpDefaults defaults;
+
+ /**
+ * Constructor, useful for DI.
+ */
+ Http(this._cookies, this._location, this._rewriter, this._backend, this.defaults, this._interceptors);
+
+ /**
+ * DEPRECATED
+ */
+ async.Future<String> getString(String url,
+ {bool withCredentials, void onProgress(dom.ProgressEvent e), Cache cache}) {
+ return request(url,
+ withCredentials: withCredentials,
+ onProgress: onProgress,
+ cache: cache).then((HttpResponse xhr) => xhr.responseText);
+ }
+
+ /**
+ * Parse a request URL and determine whether this is a same-origin request as the application document.
+ *
+ * @param {string|Uri} requestUrl The url of the request as a string that will be resolved
+ * or a parsed URL object.
+ * @returns {boolean} Whether the request is for the same origin as the application document.
+ */
+ _urlIsSameOrigin(String requestUrl) {
+ Uri originUrl = Uri.parse(_location.location.toString());
+ Uri parsed = originUrl.resolve(requestUrl);
+ return (parsed.scheme == originUrl.scheme &&
+ parsed.host == originUrl.host);
+ }
+
+/**
+ * Returns a [Future<HttpResponse>] when the request is fulfilled.
+ *
+ * Named Parameters:
+ * - method: HTTP method (e.g. 'GET', 'POST', etc)
+ * - url: Absolute or relative URL of the resource being requested.
+ * - data: Data to be sent as the request message data.
+ * - params: Map of strings or objects which will be turned to
+ * `?key1=value1&key2=value2` after the url. If the values are
+ * not strings, they will be JSONified.
+ * - headers: Map of strings or functions which return strings representing
+ * HTTP headers to send to the server. If the return value of a function
+ * is null, the header will not be sent.
+ * - xsrfHeaderName: TBI
+ * - xsrfCookieName: TBI
+ * - interceptors: Either a [HttpInterceptor] or a [HttpInterceptors]
+ * - cache: Boolean or [Cache]. If true, the default cache will be used.
+ * - timeout: deprecated
+*/
+ async.Future<HttpResponse> call({
+ String url,
+ String method,
+ data,
+ Map<String, dynamic> params,
+ Map<String, String> headers,
+ xsrfHeaderName,
+ xsrfCookieName,
+ interceptors,
+ cache,
+ timeout
+ }) {
+ if (timeout != null) {
+ throw ['timeout not implemented'];
+ }
+
+ method = method.toUpperCase();
+
+ if (headers == null) { headers = {}; }
+ defaults.headers.setHeaders(headers, method);
+
+ var xsrfValue = _urlIsSameOrigin(url) ?
+ _cookies[xsrfCookieName != null ? xsrfCookieName : defaults.xsrfCookieName] : null;
+ if (xsrfValue != null) {
+ headers[xsrfHeaderName != null ? xsrfHeaderName : defaults.xsrfHeaderName] = xsrfValue;
+ }
+
+ // Check for functions in headers
+ headers.forEach((k,v) {
+ if (v is Function) {
+ headers[k] = v();
+ }
+ });
+
+ var serverRequest = (HttpResponseConfig config) {
+ assert(config.data == null || config.data is String || config.data is dom.File);
+
+ // Strip content-type if data is undefined
+ if (config.data == null) {
+ List<String> toRemove = [];
+ headers.forEach((h, _) {
+ if (h.toUpperCase() == 'CONTENT-TYPE') {
+ toRemove.add(h);
+ };
+ });
+ toRemove.forEach((x) => headers.remove(x));
+ }
+
+
+ return request(
+ null,
+ config: config,
+ method: method,
+ sendData: config.data,
+ requestHeaders: config.headers,
+ cache: cache);
+ };
+
+ var chain = [[serverRequest, null]];
+
+ var future = new async.Future.value(new HttpResponseConfig(
+ url: url,
+ params: params,
+ headers: headers,
+ data: data));
+
+ _interceptors.constructChain(chain);
+
+ if (interceptors != null) {
+ if (interceptors is HttpInterceptor) {
+ interceptors = new HttpInterceptors.of([interceptors]);
+ }
+ assert(interceptors is HttpInterceptors);
+ interceptors.constructChain(chain);
+ }
+
+ chain.forEach((chainFns) {
+ future = future.then(chainFns[0], onError: chainFns[1]);
+ });
+
+ return future;
+ }
+
+ /**
+ * Shortcut method for GET requests. See [call] for a complete description
+ * of parameters.
+ */
+ async.Future<HttpResponse> get(String url, {
+ String data,
+ Map<String, dynamic> params,
+ Map<String, String> headers,
+ xsrfHeaderName,
+ xsrfCookieName,
+ interceptors,
+ cache,
+ timeout
+ }) => call(method: 'GET', url: url, data: data, params: params, headers: headers,
+ xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
+ interceptors: interceptors,
+ cache: cache, timeout: timeout);
+
+ /**
+ * Shortcut method for DELETE requests. See [call] for a complete description
+ * of parameters.
+ */
+ async.Future<HttpResponse> delete(String url, {
+ String data,
+ Map<String, dynamic> params,
+ Map<String, String> headers,
+ xsrfHeaderName,
+ xsrfCookieName,
+ interceptors,
+ cache,
+ timeout
+ }) => call(method: 'DELETE', url: url, data: data, params: params, headers: headers,
+ xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
+ interceptors: interceptors,
+ cache: cache, timeout: timeout);
+
+ /**
+ * Shortcut method for HEAD requests. See [call] for a complete description
+ * of parameters.
+ */
+ async.Future<HttpResponse> head(String url, {
+ String data,
+ Map<String, dynamic> params,
+ Map<String, String> headers,
+ xsrfHeaderName,
+ xsrfCookieName,
+ interceptors,
+ cache,
+ timeout
+ }) => call(method: 'HEAD', url: url, data: data, params: params, headers: headers,
+ xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
+ interceptors: interceptors,
+ cache: cache, timeout: timeout);
+
+ /**
+ * Shortcut method for PUT requests. See [call] for a complete description
+ * of parameters.
+ */
+ async.Future<HttpResponse> put(String url, String data, {
+ Map<String, dynamic> params,
+ Map<String, String> headers,
+ xsrfHeaderName,
+ xsrfCookieName,
+ interceptors,
+ cache,
+ timeout
+ }) => call(method: 'PUT', url: url, data: data, params: params, headers: headers,
+ xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
+ interceptors: interceptors,
+ cache: cache, timeout: timeout);
+
+ /**
+ * Shortcut method for POST requests. See [call] for a complete description
+ * of parameters.
+ */
+ async.Future<HttpResponse> post(String url, String data, {
+ Map<String, dynamic> params,
+ Map<String, String> headers,
+ xsrfHeaderName,
+ xsrfCookieName,
+ interceptors,
+ cache,
+ timeout
+ }) => call(method: 'POST', url: url, data: data, params: params, headers: headers,
+ xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
+ interceptors: interceptors,
+ cache: cache, timeout: timeout);
+
+ /**
+ * Shortcut method for JSONP requests. See [call] for a complete description
+ * of parameters.
+ */
+ async.Future<HttpResponse> jsonp(String url, {
+ String data,
+ Map<String, dynamic> params,
+ Map<String, String> headers,
+ xsrfHeaderName,
+ xsrfCookieName,
+ interceptors,
+ cache,
+ timeout
+ }) => call(method: 'JSONP', url: url, data: data, params: params, headers: headers,
+ xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
+ interceptors: interceptors,
+ cache: cache, timeout: timeout);
+
+ /**
+ * Parse raw headers into key-value object
+ */
+ static Map<String, String> parseHeaders(dom.HttpRequest value) {
+ var headers = value.getAllResponseHeaders();
+
+ var parsed = {}, key, val, i;
+
+ if (headers == null) return parsed;
+
+ headers.split('\n').forEach((line) {
+ i = line.indexOf(':');
+ if (i == -1) return;
+ key = line.substring(0, i).trim().toLowerCase();
+ val = line.substring(i + 1).trim();
+
+ if (key != '') {
+ if (parsed.containsKey(key)) {
+ parsed[key] += ', ' + val;
+ } else {
+ parsed[key] = val;
+ }
+ }
+ });
+ return parsed;
+ }
+
+ /**
+ * Returns an [Iterable] of [Future] [HttpResponse]s for the requests
+ * that the [Http] service is currently waiting for.
+ */
+ Iterable<async.Future<HttpResponse> > get pendingRequests =>
+ _pendingRequests.values;
+
+ /**
+ * DEPRECATED
+ */
+ async.Future<HttpResponse> request(String rawUrl,
+ { HttpResponseConfig config,
+ String method: 'GET',
+ bool withCredentials: false,
+ String responseType,
+ String mimeType,
+ Map<String, String> requestHeaders,
+ sendData,
+ void onProgress(dom.ProgressEvent e),
+ /*Cache<String, HttpResponse> or false*/ cache }) {
+ String url;
+
+ if (config == null) {
+ url = _rewriter(rawUrl);
+ config = new HttpResponseConfig(url: url);
+ } else {
+ url = _buildUrl(config.url, config.params);
+ }
+
+ if (cache is bool && cache == false) {
+ cache = null;
+ } else if (cache == null) {
+ cache = defaults.cache;
+ }
+ // We return a pending request only if caching is enabled.
+ if (cache != null && _pendingRequests.containsKey(url)) {
+ return _pendingRequests[url];
+ }
+ var cachedValue = (cache != null && method == 'GET') ? cache.get(url) : null;
+ if (cachedValue != null) {
+ return new async.Future.value(new HttpResponse.copy(cachedValue));
+ }
+
+ var result = _backend.request(url,
+ method: method,
+ withCredentials: withCredentials,
+ responseType: responseType,
+ mimeType: mimeType,
+ requestHeaders: requestHeaders,
+ sendData: sendData,
+ onProgress: onProgress).then((dom.HttpRequest value) {
+ // TODO: Uncomment after apps migrate off of this class.
+ // assert(value.status >= 200 && value.status < 300);
+
+ var response = new HttpResponse(
+ value.status, value.responseText, parseHeaders(value),
+ config);
+
+ if (cache != null) {
+ cache.put(url, response);
+ }
+ _pendingRequests.remove(url);
+ return response;
+ }, onError: (error) {
+ if (error is! dom.ProgressEvent) {
+ throw error;
+ }
+ dom.ProgressEvent event = error;
+ _pendingRequests.remove(url);
+ dom.HttpRequest request = event.currentTarget;
+ return new async.Future.error(
+ new HttpResponse(request.status, request.response,
+ parseHeaders(request), config));
+ });
+ _pendingRequests[url] = result;
+ return result;
+ }
+
+ _buildUrl(String url, Map<String, dynamic> params) {
+ if (params == null) return url;
+ var parts = [];
+
+ new List.from(params.keys)..sort()..forEach((String key) {
+ var value = params[key];
+ if (value == null) return;
+ if (value is! List) value = [value];
+
+ value.forEach((v) {
+ if (v is Map) {
+ v = JSON.encode(v);
+ }
+ parts.add(_encodeUriQuery(key) + '=' +
+ _encodeUriQuery("$v"));
+ });
+ });
+ return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
+ }
+
+ _encodeUriQuery(val, {bool pctEncodeSpaces: false}) =>
+ Uri.encodeComponent(val)
+ .replaceAll('%40', '@')
+ .replaceAll('%3A', ':')
+ .replaceAll('%24', r'$')
+ .replaceAll('%2C', ',')
+ .replaceAll('%20', pctEncodeSpaces ? '%20' : '+');
+}

Powered by Google App Engine
This is Rietveld 408576698