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

Side by Side 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, 11 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 part of angular.core.dom;
2
3 @NgInjectableService()
4 class UrlRewriter {
5 String call(url) => url;
6 }
7
8 /**
9 * HTTP backend used by the [Http] service that delegates to dart:html's
10 * [HttpRequest] and deals with Dart bugs.
11 *
12 * Never use this service directly, instead use the higher-level [Http].
13 *
14 * During testing this implementation is swapped with [MockHttpBackend] which
15 * can be trained with responses.
16 */
17 @NgInjectableService()
18 class HttpBackend {
19 /**
20 * Wrapper around dart:html's [HttpRequest.request]
21 */
22 async.Future request(String url,
23 {String method, bool withCredentials, String responseType,
24 String mimeType, Map<String, String> requestHeaders, sendData,
25 void onProgress(dom.ProgressEvent e)}) {
26 // Complete inside a then to work-around dartbug.com/13051
27 var c = new async.Completer();
28
29 dom.HttpRequest.request(url,
30 method: method,
31 withCredentials: withCredentials,
32 responseType: responseType,
33 mimeType: mimeType,
34 requestHeaders: requestHeaders,
35 sendData: sendData,
36 onProgress: onProgress).then((x) => c.complete(x));
37 return c.future;
38 }
39 }
40
41 @NgInjectableService()
42 class LocationWrapper {
43 get location => dom.window.location;
44 }
45
46 typedef RequestInterceptor(HttpResponseConfig);
47 typedef RequestErrorInterceptor(dynamic);
48 typedef Response(HttpResponse);
49 typedef ResponseError(dynamic);
50
51 /**
52 * HttpInterceptors are used to modify the Http request. They can be added to
53 * [HttpInterceptors] or passed into [Http.call].
54 */
55 class HttpInterceptor {
56 RequestInterceptor request;
57 Response response;
58 RequestErrorInterceptor requestError;
59 ResponseError responseError;
60
61 /**
62 * All parameters are optional.
63 */
64 HttpInterceptor({
65 this.request, this.response,
66 this.requestError, this.responseError});
67 }
68
69
70 /**
71 * The default transform data interceptor.abstract
72 *
73 * For requests, this interceptor will
74 * automatically stringify any non-string non-file objects.
75 *
76 * For responses, this interceptor will unwrap JSON objects and
77 * parse them into [Map]s.
78 */
79 class DefaultTransformDataHttpInterceptor implements HttpInterceptor {
80 Function request = (HttpResponseConfig config) {
81 if (config.data != null && config.data is! String && config.data is! dom.Fil e) {
82 config.data = JSON.encode(config.data);
83 }
84 return config;
85 };
86
87 static var _JSON_START = new RegExp(r'^\s*(\[|\{[^\{])');
88 static var _JSON_END = new RegExp(r'[\}\]]\s*$');
89 static var _PROTECTION_PREFIX = new RegExp('^\\)\\]\\}\',?\\n');
90 Function response = (HttpResponse r) {
91 if (r.data is String) {
92 var d = r.data;
93 d = d.replaceFirst(_PROTECTION_PREFIX, '');
94 if (d.contains(_JSON_START) && d.contains(_JSON_END)) {
95 d = JSON.decode(d);
96 }
97 return new HttpResponse.copy(r, data: d);
98 }
99 return r;
100 };
101
102 Function requestError, responseError;
103
104 }
105
106 /**
107 * A list of [HttpInterceptor]s.
108 */
109 @NgInjectableService()
110 class HttpInterceptors {
111 List<HttpInterceptor> _interceptors = [new DefaultTransformDataHttpInterceptor ()];
112
113 add(HttpInterceptor x) => _interceptors.add(x);
114 addAll(List<HttpInterceptor> x) => _interceptors.addAll(x);
115
116 /**
117 * Called from [Http] to construct a [Future] chain.
118 */
119 constructChain(List chain) {
120 _interceptors.reversed.forEach((HttpInterceptor i) {
121 // AngularJS has an optimization of not including null interceptors.
122 chain.insert(0, [
123 i.request == null ? (x) => x : i.request,
124 i.requestError]);
125 chain.add([
126 i.response == null ? (x) => x : i.response,
127 i.responseError]);
128 });
129 }
130
131 /**
132 * Default constructor.
133 */
134 HttpInterceptors() {
135 _interceptors = [new DefaultTransformDataHttpInterceptor()];
136 }
137
138 /**
139 * Creates a [HttpInterceptors] from a [List]. Does not include the default i nterceptors.
140 */
141 HttpInterceptors.of([List interceptors]) {
142 _interceptors = interceptors;
143 }
144 }
145
146 /**
147 * The request configuration of the request associated with this response.
148 */
149 class HttpResponseConfig {
150 /**
151 * The request's URL
152 */
153 String url;
154
155 /**
156 * The request params as a Map
157 */
158 Map params;
159
160 /**
161 * The header map without mangled keys
162 */
163 Map headers;
164
165 var data;
166
167
168 var _headersObj;
169
170 /**
171 * Header accessor. Given a string, it will return the matching header,
172 * case-insentivitively. Without a string, returns a header object will
173 * upper-case keys.
174 */
175 header([String name]) {
176 if (_headersObj == null) {
177 _headersObj = {};
178 headers.forEach((k,v) {
179 _headersObj[k.toLowerCase()] = v;
180 });
181 }
182
183 if (name != null) {
184 name = name.toLowerCase();
185 if (!_headersObj.containsKey(name)) return null;
186 return _headersObj[name];
187 }
188
189 return _headersObj;
190 }
191
192 /**
193 * Constructor
194 */
195 HttpResponseConfig({this.url, this.params, this.headers, this.data});
196 }
197
198 /**
199 * The response for an HTTP request. Returned from the [Http] service.
200 */
201 class HttpResponse {
202 /**
203 * The HTTP status code.
204 */
205 int status;
206
207 /**
208 * DEPRECATED
209 */
210 var responseText;
211 Map _headers;
212
213 /**
214 * The [HttpResponseConfig] object which contains the requested URL
215 */
216 HttpResponseConfig config;
217
218 /**
219 * Constructor
220 */
221 HttpResponse([this.status, this.responseText, this._headers, this.config]);
222
223 /**
224 * Copy constructor. Creates a clone of the response, optionally with new
225 * data.
226 */
227 HttpResponse.copy(HttpResponse r, {data}) {
228 status = r.status;
229 responseText = data == null ? r.responseText : data;
230 _headers = r._headers == null ? null : new Map.from(r._headers);
231 config = r.config;
232 }
233
234 /**
235 * The response's data. Either a string or a transformed object.
236 */
237 get data => responseText;
238
239 /**
240 * The response's headers. Without parameters, this method will return the
241 * [Map] of headers. With [key] parameter, this method will return the specif ic
242 * header.
243 */
244 headers([String key]) {
245 if (key == null) {
246 return _headers;
247 }
248 if (_headers.containsKey(key)) {
249 return _headers[key];
250 }
251 return null;
252 }
253
254 /**
255 * Useful for debugging.
256 */
257 toString() => 'HTTP $status: $data';
258 }
259
260 /**
261 * Default header configuration.
262 */
263 @NgInjectableService()
264 class HttpDefaultHeaders {
265 static String _defaultContentType = 'application/json;charset=utf-8';
266 Map _headers = {
267 'COMMON': {
268 'Accept': 'application/json, text/plain, */*'
269 },
270 'POST' : {
271 'Content-Type': _defaultContentType
272 },
273 'PUT' : {
274 'Content-Type': _defaultContentType
275 },
276 'PATCH' : {
277 'Content-Type': _defaultContentType
278 }
279 };
280
281 _applyHeaders(method, ucHeaders, headers) {
282 if (!_headers.containsKey(method)) return;
283 _headers[method].forEach((k, v) {
284 if (!ucHeaders.contains(k.toUpperCase())) {
285 headers[k] = v;
286 }
287 });
288 }
289
290 /**
291 * Called from [Http], this method sets default headers on [headers]
292 */
293 setHeaders(Map<String, String> headers, String method) {
294 assert(headers != null);
295 var ucHeaders = headers.keys.map((x) => x.toUpperCase()).toSet();
296 _applyHeaders('COMMON', ucHeaders, headers);
297 _applyHeaders(method.toUpperCase(), ucHeaders, headers);
298 }
299
300 /**
301 * Returns the default header [Map] for a method. You can then modify
302 * the map.
303 *
304 * Passing 'common' as [method] will return a Map that contains headers
305 * common to all operations.
306 */
307 operator[](method) {
308 return _headers[method.toUpperCase()];
309 }
310 }
311
312 /**
313 * Injected into the [Http] service. This class contains application-wide
314 * HTTP defaults.
315 *
316 * The default implementation provides headers which the
317 * Angular team believes to be useful.
318 */
319 @NgInjectableService()
320 class HttpDefaults {
321 /**
322 * The [HttpDefaultHeaders] object used by [Http] to add default headers
323 * to requests.
324 */
325 HttpDefaultHeaders headers;
326
327 /**
328 * The default cache. To enable caching application-wide, instantiate with a
329 * [Cache] object.
330 */
331 var cache;
332
333 /**
334 * The default XSRF cookie name. May not be null.
335 */
336 String xsrfCookieName = 'XSRF-TOKEN';
337
338 /**
339 * The default XSRF header name sent with the request. May not be null.
340 */
341 String xsrfHeaderName = 'X-XSRF-TOKEN';
342
343 /**
344 * Constructor intended for DI.
345 */
346 HttpDefaults(this.headers);
347 }
348
349 /**
350 * The [Http] service facilitates communication with the remote HTTP servers. I t
351 * uses dart:html's [HttpRequest] and provides a number of features on top
352 * of the core Dart library.
353 *
354 * For unit testing, applications should use the [MockHttpBackend] service.
355 *
356 * # General usage
357 * The [call] method takes a number of named parameters and returns a
358 * [Future<HttpResponse>].
359 *
360 * http(method: 'GET', url: '/someUrl')
361 * .then((HttpResponse response) { .. },
362 * onError: (HttpRequest request) { .. });
363 *
364 * A response status code between 200 and 299 is considered a success status and
365 * will result in the 'then' being called. Note that if the response is a redire ct,
366 * Dart's [HttpRequest] will transparently follow it, meaning that the error cal lback will not be
367 * called for such responses.
368 *
369 * # Shortcut methods
370 *
371 * The Http service also defines a number of shortcuts:
372 *
373 * http.get('/someUrl') is the same as http(method: 'GET', url: '/someUrl')
374 *
375 * See the method definitions below.
376 *
377 * # Setting HTTP Headers
378 *
379 * The [Http] service will add certain HTTP headers to requests. These defaults
380 * can be configured using the [HttpDefaultHeaders] object. The defaults are:
381 *
382 * - For all requests: `Accept: application/json, text/plain, * / *`
383 * - For POST, PUT, PATCH requests: `Content-Type: application/json`
384 *
385 * # Caching
386 *
387 * To enable caching, pass a [Cache] object into the [call] method. The [Http]
388 * service will store responses in the cache and return the response for
389 * any matching requests.
390 *
391 * Note that data is returned through a [Future], regardless of whether it
392 * came from the [Cache] or the server.
393 *
394 * If there are multiple GET requests for the same not-yet-in-cache URL
395 * while a cache is in use, only one request to the server will be made.
396 *
397 * # Interceptors
398 *
399 * Http uses the interceptors from [HttpInterceptors]. You can also include
400 * interceptors in the [call] method.
401 *
402 * # Security Considerations
403 *
404 * NOTE: < not yet documented >
405 */
406 @NgInjectableService()
407 class Http {
408 Map<String, async.Future<HttpResponse>> _pendingRequests = <String, async.Futu re<HttpResponse>>{};
409 BrowserCookies _cookies;
410 LocationWrapper _location;
411 UrlRewriter _rewriter;
412 HttpBackend _backend;
413 HttpInterceptors _interceptors;
414
415 /**
416 * The defaults for [Http]
417 */
418 HttpDefaults defaults;
419
420 /**
421 * Constructor, useful for DI.
422 */
423 Http(this._cookies, this._location, this._rewriter, this._backend, this.defaul ts, this._interceptors);
424
425 /**
426 * DEPRECATED
427 */
428 async.Future<String> getString(String url,
429 {bool withCredentials, void onProgress(dom.ProgressEvent e), Cache cache}) {
430 return request(url,
431 withCredentials: withCredentials,
432 onProgress: onProgress,
433 cache: cache).then((HttpResponse xhr) => xhr.responseText);
434 }
435
436 /**
437 * Parse a request URL and determine whether this is a same-origin request as the application document.
438 *
439 * @param {string|Uri} requestUrl The url of the request as a string that will be resolved
440 * or a parsed URL object.
441 * @returns {boolean} Whether the request is for the same origin as the applic ation document.
442 */
443 _urlIsSameOrigin(String requestUrl) {
444 Uri originUrl = Uri.parse(_location.location.toString());
445 Uri parsed = originUrl.resolve(requestUrl);
446 return (parsed.scheme == originUrl.scheme &&
447 parsed.host == originUrl.host);
448 }
449
450 /**
451 * Returns a [Future<HttpResponse>] when the request is fulfilled.
452 *
453 * Named Parameters:
454 * - method: HTTP method (e.g. 'GET', 'POST', etc)
455 * - url: Absolute or relative URL of the resource being requested.
456 * - data: Data to be sent as the request message data.
457 * - params: Map of strings or objects which will be turned to
458 * `?key1=value1&key2=value2` after the url. If the values are
459 * not strings, they will be JSONified.
460 * - headers: Map of strings or functions which return strings representing
461 * HTTP headers to send to the server. If the return value of a function
462 * is null, the header will not be sent.
463 * - xsrfHeaderName: TBI
464 * - xsrfCookieName: TBI
465 * - interceptors: Either a [HttpInterceptor] or a [HttpInterceptors]
466 * - cache: Boolean or [Cache]. If true, the default cache will be used.
467 * - timeout: deprecated
468 */
469 async.Future<HttpResponse> call({
470 String url,
471 String method,
472 data,
473 Map<String, dynamic> params,
474 Map<String, String> headers,
475 xsrfHeaderName,
476 xsrfCookieName,
477 interceptors,
478 cache,
479 timeout
480 }) {
481 if (timeout != null) {
482 throw ['timeout not implemented'];
483 }
484
485 method = method.toUpperCase();
486
487 if (headers == null) { headers = {}; }
488 defaults.headers.setHeaders(headers, method);
489
490 var xsrfValue = _urlIsSameOrigin(url) ?
491 _cookies[xsrfCookieName != null ? xsrfCookieName : defaults.xsrfCookieNa me] : null;
492 if (xsrfValue != null) {
493 headers[xsrfHeaderName != null ? xsrfHeaderName : defaults.xsrfHeaderName] = xsrfValue;
494 }
495
496 // Check for functions in headers
497 headers.forEach((k,v) {
498 if (v is Function) {
499 headers[k] = v();
500 }
501 });
502
503 var serverRequest = (HttpResponseConfig config) {
504 assert(config.data == null || config.data is String || config.data is dom. File);
505
506 // Strip content-type if data is undefined
507 if (config.data == null) {
508 List<String> toRemove = [];
509 headers.forEach((h, _) {
510 if (h.toUpperCase() == 'CONTENT-TYPE') {
511 toRemove.add(h);
512 };
513 });
514 toRemove.forEach((x) => headers.remove(x));
515 }
516
517
518 return request(
519 null,
520 config: config,
521 method: method,
522 sendData: config.data,
523 requestHeaders: config.headers,
524 cache: cache);
525 };
526
527 var chain = [[serverRequest, null]];
528
529 var future = new async.Future.value(new HttpResponseConfig(
530 url: url,
531 params: params,
532 headers: headers,
533 data: data));
534
535 _interceptors.constructChain(chain);
536
537 if (interceptors != null) {
538 if (interceptors is HttpInterceptor) {
539 interceptors = new HttpInterceptors.of([interceptors]);
540 }
541 assert(interceptors is HttpInterceptors);
542 interceptors.constructChain(chain);
543 }
544
545 chain.forEach((chainFns) {
546 future = future.then(chainFns[0], onError: chainFns[1]);
547 });
548
549 return future;
550 }
551
552 /**
553 * Shortcut method for GET requests. See [call] for a complete description
554 * of parameters.
555 */
556 async.Future<HttpResponse> get(String url, {
557 String data,
558 Map<String, dynamic> params,
559 Map<String, String> headers,
560 xsrfHeaderName,
561 xsrfCookieName,
562 interceptors,
563 cache,
564 timeout
565 }) => call(method: 'GET', url: url, data: data, params: params, headers: heade rs,
566 xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
567 interceptors: interceptors,
568 cache: cache, timeout: timeout);
569
570 /**
571 * Shortcut method for DELETE requests. See [call] for a complete description
572 * of parameters.
573 */
574 async.Future<HttpResponse> delete(String url, {
575 String data,
576 Map<String, dynamic> params,
577 Map<String, String> headers,
578 xsrfHeaderName,
579 xsrfCookieName,
580 interceptors,
581 cache,
582 timeout
583 }) => call(method: 'DELETE', url: url, data: data, params: params, headers: he aders,
584 xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
585 interceptors: interceptors,
586 cache: cache, timeout: timeout);
587
588 /**
589 * Shortcut method for HEAD requests. See [call] for a complete description
590 * of parameters.
591 */
592 async.Future<HttpResponse> head(String url, {
593 String data,
594 Map<String, dynamic> params,
595 Map<String, String> headers,
596 xsrfHeaderName,
597 xsrfCookieName,
598 interceptors,
599 cache,
600 timeout
601 }) => call(method: 'HEAD', url: url, data: data, params: params, headers: head ers,
602 xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
603 interceptors: interceptors,
604 cache: cache, timeout: timeout);
605
606 /**
607 * Shortcut method for PUT requests. See [call] for a complete description
608 * of parameters.
609 */
610 async.Future<HttpResponse> put(String url, String data, {
611 Map<String, dynamic> params,
612 Map<String, String> headers,
613 xsrfHeaderName,
614 xsrfCookieName,
615 interceptors,
616 cache,
617 timeout
618 }) => call(method: 'PUT', url: url, data: data, params: params, headers: heade rs,
619 xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
620 interceptors: interceptors,
621 cache: cache, timeout: timeout);
622
623 /**
624 * Shortcut method for POST requests. See [call] for a complete description
625 * of parameters.
626 */
627 async.Future<HttpResponse> post(String url, String data, {
628 Map<String, dynamic> params,
629 Map<String, String> headers,
630 xsrfHeaderName,
631 xsrfCookieName,
632 interceptors,
633 cache,
634 timeout
635 }) => call(method: 'POST', url: url, data: data, params: params, headers: head ers,
636 xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
637 interceptors: interceptors,
638 cache: cache, timeout: timeout);
639
640 /**
641 * Shortcut method for JSONP requests. See [call] for a complete description
642 * of parameters.
643 */
644 async.Future<HttpResponse> jsonp(String url, {
645 String data,
646 Map<String, dynamic> params,
647 Map<String, String> headers,
648 xsrfHeaderName,
649 xsrfCookieName,
650 interceptors,
651 cache,
652 timeout
653 }) => call(method: 'JSONP', url: url, data: data, params: params, headers: hea ders,
654 xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName,
655 interceptors: interceptors,
656 cache: cache, timeout: timeout);
657
658 /**
659 * Parse raw headers into key-value object
660 */
661 static Map<String, String> parseHeaders(dom.HttpRequest value) {
662 var headers = value.getAllResponseHeaders();
663
664 var parsed = {}, key, val, i;
665
666 if (headers == null) return parsed;
667
668 headers.split('\n').forEach((line) {
669 i = line.indexOf(':');
670 if (i == -1) return;
671 key = line.substring(0, i).trim().toLowerCase();
672 val = line.substring(i + 1).trim();
673
674 if (key != '') {
675 if (parsed.containsKey(key)) {
676 parsed[key] += ', ' + val;
677 } else {
678 parsed[key] = val;
679 }
680 }
681 });
682 return parsed;
683 }
684
685 /**
686 * Returns an [Iterable] of [Future] [HttpResponse]s for the requests
687 * that the [Http] service is currently waiting for.
688 */
689 Iterable<async.Future<HttpResponse> > get pendingRequests =>
690 _pendingRequests.values;
691
692 /**
693 * DEPRECATED
694 */
695 async.Future<HttpResponse> request(String rawUrl,
696 { HttpResponseConfig config,
697 String method: 'GET',
698 bool withCredentials: false,
699 String responseType,
700 String mimeType,
701 Map<String, String> requestHeaders,
702 sendData,
703 void onProgress(dom.ProgressEvent e),
704 /*Cache<String, HttpResponse> or false*/ cache }) {
705 String url;
706
707 if (config == null) {
708 url = _rewriter(rawUrl);
709 config = new HttpResponseConfig(url: url);
710 } else {
711 url = _buildUrl(config.url, config.params);
712 }
713
714 if (cache is bool && cache == false) {
715 cache = null;
716 } else if (cache == null) {
717 cache = defaults.cache;
718 }
719 // We return a pending request only if caching is enabled.
720 if (cache != null && _pendingRequests.containsKey(url)) {
721 return _pendingRequests[url];
722 }
723 var cachedValue = (cache != null && method == 'GET') ? cache.get(url) : null ;
724 if (cachedValue != null) {
725 return new async.Future.value(new HttpResponse.copy(cachedValue));
726 }
727
728 var result = _backend.request(url,
729 method: method,
730 withCredentials: withCredentials,
731 responseType: responseType,
732 mimeType: mimeType,
733 requestHeaders: requestHeaders,
734 sendData: sendData,
735 onProgress: onProgress).then((dom.HttpRequest value) {
736 // TODO: Uncomment after apps migrate off of this class.
737 // assert(value.status >= 200 && value.status < 300);
738
739 var response = new HttpResponse(
740 value.status, value.responseText, parseHeaders(value),
741 config);
742
743 if (cache != null) {
744 cache.put(url, response);
745 }
746 _pendingRequests.remove(url);
747 return response;
748 }, onError: (error) {
749 if (error is! dom.ProgressEvent) {
750 throw error;
751 }
752 dom.ProgressEvent event = error;
753 _pendingRequests.remove(url);
754 dom.HttpRequest request = event.currentTarget;
755 return new async.Future.error(
756 new HttpResponse(request.status, request.response,
757 parseHeaders(request), config));
758 });
759 _pendingRequests[url] = result;
760 return result;
761 }
762
763 _buildUrl(String url, Map<String, dynamic> params) {
764 if (params == null) return url;
765 var parts = [];
766
767 new List.from(params.keys)..sort()..forEach((String key) {
768 var value = params[key];
769 if (value == null) return;
770 if (value is! List) value = [value];
771
772 value.forEach((v) {
773 if (v is Map) {
774 v = JSON.encode(v);
775 }
776 parts.add(_encodeUriQuery(key) + '=' +
777 _encodeUriQuery("$v"));
778 });
779 });
780 return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
781 }
782
783 _encodeUriQuery(val, {bool pctEncodeSpaces: false}) =>
784 Uri.encodeComponent(val)
785 .replaceAll('%40', '@')
786 .replaceAll('%3A', ':')
787 .replaceAll('%24', r'$')
788 .replaceAll('%2C', ',')
789 .replaceAll('%20', pctEncodeSpaces ? '%20' : '+');
790 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698