| 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 |