OLD | NEW |
1 part of angular.core.dom; | 1 part of angular.core.dom_internal; |
2 | 2 |
3 @NgInjectableService() | 3 @Injectable() |
4 class UrlRewriter { | 4 class UrlRewriter { |
5 String call(url) => url; | 5 String call(url) => url; |
6 } | 6 } |
7 | 7 |
8 /** | 8 /** |
9 * HTTP backend used by the [Http] service that delegates to dart:html's | 9 * HTTP backend used by the [Http] service that delegates to dart:html's |
10 * [HttpRequest] and deals with Dart bugs. | 10 * [HttpRequest] and deals with Dart bugs. |
11 * | 11 * |
12 * Never use this service directly, instead use the higher-level [Http]. | 12 * Never use this service directly, instead use the higher-level [Http]. |
13 * | 13 * |
14 * During testing this implementation is swapped with [MockHttpBackend] which | 14 * During testing this implementation is swapped with [MockHttpBackend] which |
15 * can be trained with responses. | 15 * can be trained with responses. |
16 */ | 16 */ |
17 @NgInjectableService() | 17 @Injectable() |
18 class HttpBackend { | 18 class HttpBackend { |
19 /** | 19 /** |
20 * Wrapper around dart:html's [HttpRequest.request] | 20 * Wrapper around dart:html's [HttpRequest.request] |
21 */ | 21 */ |
22 async.Future request(String url, | 22 async.Future request(String url, |
23 {String method, bool withCredentials, String responseType, | 23 {String method, bool withCredentials, String responseType, |
24 String mimeType, Map<String, String> requestHeaders, sendData, | 24 String mimeType, Map<String, String> requestHeaders, sendData, |
25 void onProgress(dom.ProgressEvent e)}) => | 25 void onProgress(dom.ProgressEvent e)}) => |
26 dom.HttpRequest.request(url, method: method, | 26 dom.HttpRequest.request(url, method: method, |
27 withCredentials: withCredentials, responseType: responseType, | 27 withCredentials: withCredentials, responseType: responseType, |
28 mimeType: mimeType, requestHeaders: requestHeaders, | 28 mimeType: mimeType, requestHeaders: requestHeaders, |
29 sendData: sendData, onProgress: onProgress); | 29 sendData: sendData, onProgress: onProgress); |
30 } | 30 } |
31 | 31 |
32 @NgInjectableService() | 32 @Injectable() |
33 class LocationWrapper { | 33 class LocationWrapper { |
34 get location => dom.window.location; | 34 get location => dom.window.location; |
35 } | 35 } |
36 | 36 |
37 typedef RequestInterceptor(HttpResponseConfig); | 37 typedef RequestInterceptor(HttpResponseConfig); |
38 typedef RequestErrorInterceptor(dynamic); | 38 typedef RequestErrorInterceptor(dynamic); |
39 typedef Response(HttpResponse); | 39 typedef Response(HttpResponse); |
40 typedef ResponseError(dynamic); | 40 typedef ResponseError(dynamic); |
41 | 41 |
42 /** | 42 /** |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
88 } | 88 } |
89 return r; | 89 return r; |
90 }; | 90 }; |
91 | 91 |
92 Function requestError, responseError; | 92 Function requestError, responseError; |
93 } | 93 } |
94 | 94 |
95 /** | 95 /** |
96 * A list of [HttpInterceptor]s. | 96 * A list of [HttpInterceptor]s. |
97 */ | 97 */ |
98 @NgInjectableService() | 98 @Injectable() |
99 class HttpInterceptors { | 99 class HttpInterceptors { |
100 List<HttpInterceptor> _interceptors = | 100 List<HttpInterceptor> _interceptors = |
101 [new DefaultTransformDataHttpInterceptor()]; | 101 [new DefaultTransformDataHttpInterceptor()]; |
102 | 102 |
103 add(HttpInterceptor x) => _interceptors.add(x); | 103 add(HttpInterceptor x) => _interceptors.add(x); |
104 addAll(List<HttpInterceptor> x) => _interceptors.addAll(x); | 104 addAll(List<HttpInterceptor> x) => _interceptors.addAll(x); |
105 | 105 |
106 /** | 106 /** |
107 * Called from [Http] to construct a [Future] chain. | 107 * Called from [Http] to construct a [Future] chain. |
108 */ | 108 */ |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
227 | 227 |
228 /** | 228 /** |
229 * Useful for debugging. | 229 * Useful for debugging. |
230 */ | 230 */ |
231 toString() => 'HTTP $status: $data'; | 231 toString() => 'HTTP $status: $data'; |
232 } | 232 } |
233 | 233 |
234 /** | 234 /** |
235 * Default header configuration. | 235 * Default header configuration. |
236 */ | 236 */ |
237 @NgInjectableService() | 237 @Injectable() |
238 class HttpDefaultHeaders { | 238 class HttpDefaultHeaders { |
239 static var _defaultContentType = 'application/json;charset=utf-8'; | 239 static var _defaultContentType = 'application/json;charset=utf-8'; |
240 var _headers = { | 240 var _headers = { |
241 'COMMON': {'Accept': 'application/json, text/plain, */*'}, | 241 'COMMON': {'Accept': 'application/json, text/plain, */*'}, |
242 'POST' : {'Content-Type': _defaultContentType}, | 242 'POST' : {'Content-Type': _defaultContentType}, |
243 'PUT' : {'Content-Type': _defaultContentType }, | 243 'PUT' : {'Content-Type': _defaultContentType }, |
244 'PATCH' : {'Content-Type': _defaultContentType} | 244 'PATCH' : {'Content-Type': _defaultContentType} |
245 }; | 245 }; |
246 | 246 |
247 _applyHeaders(method, ucHeaders, headers) { | 247 _applyHeaders(method, ucHeaders, headers) { |
(...skipping 25 matching lines...) Expand all Loading... |
273 operator[](method) => _headers[method.toUpperCase()]; | 273 operator[](method) => _headers[method.toUpperCase()]; |
274 } | 274 } |
275 | 275 |
276 /** | 276 /** |
277 * Injected into the [Http] service. This class contains application-wide | 277 * Injected into the [Http] service. This class contains application-wide |
278 * HTTP defaults. | 278 * HTTP defaults. |
279 * | 279 * |
280 * The default implementation provides headers which the | 280 * The default implementation provides headers which the |
281 * Angular team believes to be useful. | 281 * Angular team believes to be useful. |
282 */ | 282 */ |
283 @NgInjectableService() | 283 @Injectable() |
284 class HttpDefaults { | 284 class HttpDefaults { |
285 /** | 285 /** |
286 * The [HttpDefaultHeaders] object used by [Http] to add default headers | 286 * The [HttpDefaultHeaders] object used by [Http] to add default headers |
287 * to requests. | 287 * to requests. |
288 */ | 288 */ |
289 HttpDefaultHeaders headers; | 289 HttpDefaultHeaders headers; |
290 | 290 |
291 /** | 291 /** |
292 * The default cache. To enable caching application-wide, instantiate with a | 292 * The default cache. To enable caching application-wide, instantiate with a |
293 * [Cache] object. | 293 * [Cache] object. |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
360 * | 360 * |
361 * # Interceptors | 361 * # Interceptors |
362 * | 362 * |
363 * Http uses the interceptors from [HttpInterceptors]. You can also include | 363 * Http uses the interceptors from [HttpInterceptors]. You can also include |
364 * interceptors in the [call] method. | 364 * interceptors in the [call] method. |
365 * | 365 * |
366 * # Security Considerations | 366 * # Security Considerations |
367 * | 367 * |
368 * NOTE: < not yet documented > | 368 * NOTE: < not yet documented > |
369 */ | 369 */ |
370 @NgInjectableService() | 370 @Injectable() |
371 class Http { | 371 class Http { |
372 var _pendingRequests = <String, async.Future<HttpResponse>>{}; | 372 var _pendingRequests = <String, async.Future<HttpResponse>>{}; |
373 BrowserCookies _cookies; | 373 BrowserCookies _cookies; |
374 LocationWrapper _location; | 374 LocationWrapper _location; |
375 UrlRewriter _rewriter; | 375 UrlRewriter _rewriter; |
376 HttpBackend _backend; | 376 HttpBackend _backend; |
377 HttpInterceptors _interceptors; | 377 HttpInterceptors _interceptors; |
378 | 378 |
379 /** | 379 /** |
380 * The defaults for [Http] | 380 * The defaults for [Http] |
381 */ | 381 */ |
382 HttpDefaults defaults; | 382 HttpDefaults defaults; |
383 | 383 |
384 /** | 384 /** |
385 * Constructor, useful for DI. | 385 * Constructor, useful for DI. |
386 */ | 386 */ |
387 Http(this._cookies, this._location, this._rewriter, this._backend, | 387 Http(this._cookies, this._location, this._rewriter, this._backend, |
388 this.defaults, this._interceptors); | 388 this.defaults, this._interceptors); |
389 | 389 |
390 /** | 390 /** |
391 * DEPRECATED | |
392 */ | |
393 async.Future<String> getString(String url, {bool withCredentials, | |
394 void onProgress(dom.ProgressEvent e), Cache cache}) => | |
395 request(url, | |
396 withCredentials: withCredentials, | |
397 onProgress: onProgress, | |
398 cache: cache).then((HttpResponse xhr) => xhr.responseText); | |
399 | |
400 /** | |
401 * Parse a [requestUrl] and determine whether this is a same-origin request as | 391 * Parse a [requestUrl] and determine whether this is a same-origin request as |
402 * the application document. | 392 * the application document. |
403 */ | 393 */ |
404 bool _urlIsSameOrigin(String requestUrl) { | 394 bool _urlIsSameOrigin(String requestUrl) { |
405 Uri originUrl = Uri.parse(_location.location.toString()); | 395 Uri originUrl = Uri.parse(_location.location.toString()); |
406 Uri parsed = originUrl.resolve(requestUrl); | 396 Uri parsed = originUrl.resolve(requestUrl); |
407 return (parsed.scheme == originUrl.scheme && parsed.host == originUrl.host); | 397 return (parsed.scheme == originUrl.scheme && parsed.host == originUrl.host); |
408 } | 398 } |
409 | 399 |
410 /** | 400 /** |
(...skipping 13 matching lines...) Expand all Loading... |
424 * - xsrfCookieName: TBI | 414 * - xsrfCookieName: TBI |
425 * - interceptors: Either a [HttpInterceptor] or a [HttpInterceptors] | 415 * - interceptors: Either a [HttpInterceptor] or a [HttpInterceptors] |
426 * - cache: Boolean or [Cache]. If true, the default cache will be used. | 416 * - cache: Boolean or [Cache]. If true, the default cache will be used. |
427 * - timeout: deprecated | 417 * - timeout: deprecated |
428 */ | 418 */ |
429 async.Future<HttpResponse> call({ | 419 async.Future<HttpResponse> call({ |
430 String url, | 420 String url, |
431 String method, | 421 String method, |
432 data, | 422 data, |
433 Map<String, dynamic> params, | 423 Map<String, dynamic> params, |
434 Map<String, String> headers, | 424 Map<String, dynamic> headers, |
435 xsrfHeaderName, | 425 xsrfHeaderName, |
436 xsrfCookieName, | 426 xsrfCookieName, |
437 interceptors, | 427 interceptors, |
438 cache, | 428 cache, |
439 timeout | 429 timeout |
440 }) { | 430 }) { |
441 if (timeout != null) { | 431 if (timeout != null) { |
442 throw ['timeout not implemented']; | 432 throw ['timeout not implemented']; |
443 } | 433 } |
444 | 434 |
| 435 url = _rewriter(url); |
445 method = method.toUpperCase(); | 436 method = method.toUpperCase(); |
446 | 437 |
447 if (headers == null) headers = {}; | 438 if (headers == null) headers = {}; |
448 defaults.headers.setHeaders(headers, method); | 439 defaults.headers.setHeaders(headers, method); |
449 | 440 |
450 var xsrfValue = _urlIsSameOrigin(url) ? | 441 var xsrfValue = _urlIsSameOrigin(url) ? |
451 _cookies[xsrfCookieName != null ? xsrfCookieName : defaults.xsrfCookieNa
me] : | 442 _cookies[xsrfCookieName != null ? xsrfCookieName : defaults.xsrfCookieNa
me] : |
452 null; | 443 null; |
453 if (xsrfValue != null) { | 444 if (xsrfValue != null) { |
454 headers[xsrfHeaderName != null ? xsrfHeaderName : defaults.xsrfHeaderName] | 445 headers[xsrfHeaderName != null ? xsrfHeaderName : defaults.xsrfHeaderName] |
455 = xsrfValue; | 446 = xsrfValue; |
456 } | 447 } |
457 | 448 |
458 // Check for functions in headers | 449 // Check for functions in headers |
459 headers.forEach((k, v) { | 450 headers.forEach((k, v) { |
460 if (v is Function) headers[k] = v(); | 451 if (v is Function) headers[k] = v(); |
461 }); | 452 }); |
462 | 453 |
463 var serverRequest = (HttpResponseConfig config) { | 454 var serverRequest = (HttpResponseConfig config) { |
464 assert(config.data == null || config.data is String || | 455 assert(config.data == null || config.data is String || config.data is dom.
File); |
465 config.data is dom.File); | |
466 | 456 |
467 // Strip content-type if data is undefined | 457 // Strip content-type if data is undefined |
468 if (config.data == null) { | 458 if (config.data == null) { |
469 new List.from(headers.keys) | 459 new List.from(headers.keys) |
470 .where((h) => h.toUpperCase() == 'CONTENT-TYPE') | 460 .where((h) => h.toUpperCase() == 'CONTENT-TYPE') |
471 .forEach((h) => headers.remove(h)); | 461 .forEach((h) => headers.remove(h)); |
472 } | 462 } |
473 | 463 |
474 return request(null, | 464 url = _buildUrl(config.url, config.params); |
475 config: config, | 465 |
476 method: method, | 466 if (cache == false) { |
477 sendData: config.data, | 467 cache = null; |
478 requestHeaders: config.headers, | 468 } else if (cache == null) { |
479 cache: cache); | 469 cache = defaults.cache; |
| 470 } |
| 471 |
| 472 // We return a pending request only if caching is enabled. |
| 473 if (cache != null && _pendingRequests.containsKey(url)) { |
| 474 return _pendingRequests[url]; |
| 475 } |
| 476 var cachedResponse = (cache != null && method == 'GET') ? cache.get(url) :
null; |
| 477 if (cachedResponse != null) { |
| 478 return new async.Future.value(new HttpResponse.copy(cachedResponse)); |
| 479 } |
| 480 |
| 481 var result = _backend.request(url, |
| 482 method: method, |
| 483 requestHeaders: config.headers, |
| 484 sendData: config.data).then((dom.HttpRequest
value) { |
| 485 // TODO: Uncomment after apps migrate off of this class. |
| 486 // assert(value.status >= 200 && value.status < 300); |
| 487 |
| 488 var response = new HttpResponse(value.status, value.responseText, |
| 489 parseHeaders(value), config); |
| 490 |
| 491 if (cache != null) cache.put(url, response); |
| 492 _pendingRequests.remove(url); |
| 493 return response; |
| 494 }, onError: (error) { |
| 495 if (error is! dom.ProgressEvent) throw error; |
| 496 dom.ProgressEvent event = error; |
| 497 _pendingRequests.remove(url); |
| 498 dom.HttpRequest request = event.currentTarget; |
| 499 return new async.Future.error( |
| 500 new HttpResponse(request.status, request.response, parseHeaders(requ
est), config)); |
| 501 }); |
| 502 return _pendingRequests[url] = result; |
480 }; | 503 }; |
481 | 504 |
482 var chain = [[serverRequest, null]]; | 505 var chain = [[serverRequest, null]]; |
483 | 506 |
484 var future = new async.Future.value(new HttpResponseConfig( | 507 var future = new async.Future.value(new HttpResponseConfig( |
485 url: url, | 508 url: url, |
486 params: params, | 509 params: params, |
487 headers: headers, | 510 headers: headers, |
488 data: data)); | 511 data: data)); |
489 | 512 |
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
625 if (i == -1) return; | 648 if (i == -1) return; |
626 var key = line.substring(0, i).trim().toLowerCase(); | 649 var key = line.substring(0, i).trim().toLowerCase(); |
627 | 650 |
628 if (key.isNotEmpty) { | 651 if (key.isNotEmpty) { |
629 var val = line.substring(i + 1).trim(); | 652 var val = line.substring(i + 1).trim(); |
630 parsed[key] = parsed.containsKey(key) ? "${parsed[key]}, $val" : val; | 653 parsed[key] = parsed.containsKey(key) ? "${parsed[key]}, $val" : val; |
631 } | 654 } |
632 }); | 655 }); |
633 return parsed; | 656 return parsed; |
634 } | 657 } |
635 | |
636 /** | 658 /** |
637 * Returns an [Iterable] of [Future] [HttpResponse]s for the requests | 659 * Returns an [Iterable] of [Future] [HttpResponse]s for the requests |
638 * that the [Http] service is currently waiting for. | 660 * that the [Http] service is currently waiting for. |
639 */ | 661 */ |
640 Iterable<async.Future<HttpResponse> > get pendingRequests => | 662 Iterable<async.Future<HttpResponse> > get pendingRequests => |
641 _pendingRequests.values; | 663 _pendingRequests.values; |
642 | 664 |
643 /** | |
644 * DEPRECATED | |
645 */ | |
646 async.Future<HttpResponse> request(String rawUrl, | |
647 { HttpResponseConfig config, | |
648 String method: 'GET', | |
649 bool withCredentials: false, | |
650 String responseType, | |
651 String mimeType, | |
652 Map<String, String> requestHeaders, | |
653 sendData, | |
654 void onProgress(dom.ProgressEvent e), | |
655 /*Cache<String, HttpResponse> or false*/ cache }) { | |
656 String url; | |
657 | |
658 if (config == null) { | |
659 url = _rewriter(rawUrl); | |
660 config = new HttpResponseConfig(url: url); | |
661 } else { | |
662 url = _buildUrl(config.url, config.params); | |
663 } | |
664 | |
665 if (cache == false) { | |
666 cache = null; | |
667 } else if (cache == null) { | |
668 cache = defaults.cache; | |
669 } | |
670 // We return a pending request only if caching is enabled. | |
671 if (cache != null && _pendingRequests.containsKey(url)) { | |
672 return _pendingRequests[url]; | |
673 } | |
674 var cachedResponse = (cache != null && method == 'GET') | |
675 ? cache.get(url) | |
676 : null; | |
677 if (cachedResponse != null) { | |
678 return new async.Future.value(new HttpResponse.copy(cachedResponse)); | |
679 } | |
680 | |
681 var result = _backend.request(url, | |
682 method: method, | |
683 withCredentials: withCredentials, | |
684 responseType: responseType, | |
685 mimeType: mimeType, | |
686 requestHeaders: requestHeaders, | |
687 sendData: sendData, | |
688 onProgress: onProgress).then((dom.HttpRequest value) { | |
689 // TODO: Uncomment after apps migrate off of this class. | |
690 // assert(value.status >= 200 && value.status < 300); | |
691 | |
692 var response = new HttpResponse(value.status, value.responseText, | |
693 parseHeaders(value), config); | |
694 | |
695 if (cache != null) cache.put(url, response); | |
696 _pendingRequests.remove(url); | |
697 return response; | |
698 }, onError: (error) { | |
699 if (error is! dom.ProgressEvent) throw error; | |
700 dom.ProgressEvent event = error; | |
701 _pendingRequests.remove(url); | |
702 dom.HttpRequest request = event.currentTarget; | |
703 return new async.Future.error( | |
704 new HttpResponse(request.status, request.response, | |
705 parseHeaders(request), config)); | |
706 }); | |
707 return _pendingRequests[url] = result; | |
708 } | |
709 | |
710 _buildUrl(String url, Map<String, dynamic> params) { | 665 _buildUrl(String url, Map<String, dynamic> params) { |
711 if (params == null) return url; | 666 if (params == null) return url; |
712 var parts = []; | 667 var parts = []; |
713 | 668 |
714 new List.from(params.keys)..sort()..forEach((String key) { | 669 new List.from(params.keys)..sort()..forEach((String key) { |
715 var value = params[key]; | 670 var value = params[key]; |
716 if (value == null) return; | 671 if (value == null) return; |
717 if (value is! List) value = [value]; | 672 if (value is! List) value = [value]; |
718 | 673 |
719 value.forEach((v) { | 674 value.forEach((v) { |
720 if (v is Map) v = JSON.encode(v); | 675 if (v is Map) v = JSON.encode(v); |
721 parts.add(_encodeUriQuery(key) + '=' + _encodeUriQuery("$v")); | 676 parts.add(_encodeUriQuery(key) + '=' + _encodeUriQuery("$v")); |
722 }); | 677 }); |
723 }); | 678 }); |
724 return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); | 679 return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); |
725 } | 680 } |
726 | 681 |
727 _encodeUriQuery(val, {bool pctEncodeSpaces: false}) => | 682 _encodeUriQuery(val, {bool pctEncodeSpaces: false}) => |
728 Uri.encodeComponent(val) | 683 Uri.encodeComponent(val) |
729 .replaceAll('%40', '@') | 684 .replaceAll('%40', '@') |
730 .replaceAll('%3A', ':') | 685 .replaceAll('%3A', ':') |
731 .replaceAll('%24', r'$') | 686 .replaceAll('%24', r'$') |
732 .replaceAll('%2C', ',') | 687 .replaceAll('%2C', ',') |
733 .replaceAll('%20', pctEncodeSpaces ? '%20' : '+'); | 688 .replaceAll('%20', pctEncodeSpaces ? '%20' : '+'); |
734 } | 689 } |
OLD | NEW |