| OLD | NEW |
| 1 part of angular.core.dom_internal; | 1 part of angular.core.dom; |
| 2 | 2 |
| 3 @Injectable() | 3 @NgInjectableService() |
| 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 @Injectable() | 17 @NgInjectableService() |
| 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 @Injectable() | 32 @NgInjectableService() |
| 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 @Injectable() | 98 @NgInjectableService() |
| 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 @Injectable() | 237 @NgInjectableService() |
| 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 @Injectable() | 283 @NgInjectableService() |
| 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 @Injectable() | 370 @NgInjectableService() |
| 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 /** |
| 391 * Parse a [requestUrl] and determine whether this is a same-origin request as | 401 * Parse a [requestUrl] and determine whether this is a same-origin request as |
| 392 * the application document. | 402 * the application document. |
| 393 */ | 403 */ |
| 394 bool _urlIsSameOrigin(String requestUrl) { | 404 bool _urlIsSameOrigin(String requestUrl) { |
| 395 Uri originUrl = Uri.parse(_location.location.toString()); | 405 Uri originUrl = Uri.parse(_location.location.toString()); |
| 396 Uri parsed = originUrl.resolve(requestUrl); | 406 Uri parsed = originUrl.resolve(requestUrl); |
| 397 return (parsed.scheme == originUrl.scheme && parsed.host == originUrl.host); | 407 return (parsed.scheme == originUrl.scheme && parsed.host == originUrl.host); |
| 398 } | 408 } |
| 399 | 409 |
| 400 /** | 410 /** |
| (...skipping 13 matching lines...) Expand all Loading... |
| 414 * - xsrfCookieName: TBI | 424 * - xsrfCookieName: TBI |
| 415 * - interceptors: Either a [HttpInterceptor] or a [HttpInterceptors] | 425 * - interceptors: Either a [HttpInterceptor] or a [HttpInterceptors] |
| 416 * - cache: Boolean or [Cache]. If true, the default cache will be used. | 426 * - cache: Boolean or [Cache]. If true, the default cache will be used. |
| 417 * - timeout: deprecated | 427 * - timeout: deprecated |
| 418 */ | 428 */ |
| 419 async.Future<HttpResponse> call({ | 429 async.Future<HttpResponse> call({ |
| 420 String url, | 430 String url, |
| 421 String method, | 431 String method, |
| 422 data, | 432 data, |
| 423 Map<String, dynamic> params, | 433 Map<String, dynamic> params, |
| 424 Map<String, dynamic> headers, | 434 Map<String, String> headers, |
| 425 xsrfHeaderName, | 435 xsrfHeaderName, |
| 426 xsrfCookieName, | 436 xsrfCookieName, |
| 427 interceptors, | 437 interceptors, |
| 428 cache, | 438 cache, |
| 429 timeout | 439 timeout |
| 430 }) { | 440 }) { |
| 431 if (timeout != null) { | 441 if (timeout != null) { |
| 432 throw ['timeout not implemented']; | 442 throw ['timeout not implemented']; |
| 433 } | 443 } |
| 434 | 444 |
| 435 url = _rewriter(url); | |
| 436 method = method.toUpperCase(); | 445 method = method.toUpperCase(); |
| 437 | 446 |
| 438 if (headers == null) headers = {}; | 447 if (headers == null) headers = {}; |
| 439 defaults.headers.setHeaders(headers, method); | 448 defaults.headers.setHeaders(headers, method); |
| 440 | 449 |
| 441 var xsrfValue = _urlIsSameOrigin(url) ? | 450 var xsrfValue = _urlIsSameOrigin(url) ? |
| 442 _cookies[xsrfCookieName != null ? xsrfCookieName : defaults.xsrfCookieNa
me] : | 451 _cookies[xsrfCookieName != null ? xsrfCookieName : defaults.xsrfCookieNa
me] : |
| 443 null; | 452 null; |
| 444 if (xsrfValue != null) { | 453 if (xsrfValue != null) { |
| 445 headers[xsrfHeaderName != null ? xsrfHeaderName : defaults.xsrfHeaderName] | 454 headers[xsrfHeaderName != null ? xsrfHeaderName : defaults.xsrfHeaderName] |
| 446 = xsrfValue; | 455 = xsrfValue; |
| 447 } | 456 } |
| 448 | 457 |
| 449 // Check for functions in headers | 458 // Check for functions in headers |
| 450 headers.forEach((k, v) { | 459 headers.forEach((k, v) { |
| 451 if (v is Function) headers[k] = v(); | 460 if (v is Function) headers[k] = v(); |
| 452 }); | 461 }); |
| 453 | 462 |
| 454 var serverRequest = (HttpResponseConfig config) { | 463 var serverRequest = (HttpResponseConfig config) { |
| 455 assert(config.data == null || config.data is String || config.data is dom.
File); | 464 assert(config.data == null || config.data is String || |
| 465 config.data is dom.File); |
| 456 | 466 |
| 457 // Strip content-type if data is undefined | 467 // Strip content-type if data is undefined |
| 458 if (config.data == null) { | 468 if (config.data == null) { |
| 459 new List.from(headers.keys) | 469 new List.from(headers.keys) |
| 460 .where((h) => h.toUpperCase() == 'CONTENT-TYPE') | 470 .where((h) => h.toUpperCase() == 'CONTENT-TYPE') |
| 461 .forEach((h) => headers.remove(h)); | 471 .forEach((h) => headers.remove(h)); |
| 462 } | 472 } |
| 463 | 473 |
| 464 url = _buildUrl(config.url, config.params); | 474 return request(null, |
| 465 | 475 config: config, |
| 466 if (cache == false) { | 476 method: method, |
| 467 cache = null; | 477 sendData: config.data, |
| 468 } else if (cache == null) { | 478 requestHeaders: config.headers, |
| 469 cache = defaults.cache; | 479 cache: 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; | |
| 503 }; | 480 }; |
| 504 | 481 |
| 505 var chain = [[serverRequest, null]]; | 482 var chain = [[serverRequest, null]]; |
| 506 | 483 |
| 507 var future = new async.Future.value(new HttpResponseConfig( | 484 var future = new async.Future.value(new HttpResponseConfig( |
| 508 url: url, | 485 url: url, |
| 509 params: params, | 486 params: params, |
| 510 headers: headers, | 487 headers: headers, |
| 511 data: data)); | 488 data: data)); |
| 512 | 489 |
| (...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 648 if (i == -1) return; | 625 if (i == -1) return; |
| 649 var key = line.substring(0, i).trim().toLowerCase(); | 626 var key = line.substring(0, i).trim().toLowerCase(); |
| 650 | 627 |
| 651 if (key.isNotEmpty) { | 628 if (key.isNotEmpty) { |
| 652 var val = line.substring(i + 1).trim(); | 629 var val = line.substring(i + 1).trim(); |
| 653 parsed[key] = parsed.containsKey(key) ? "${parsed[key]}, $val" : val; | 630 parsed[key] = parsed.containsKey(key) ? "${parsed[key]}, $val" : val; |
| 654 } | 631 } |
| 655 }); | 632 }); |
| 656 return parsed; | 633 return parsed; |
| 657 } | 634 } |
| 635 |
| 658 /** | 636 /** |
| 659 * Returns an [Iterable] of [Future] [HttpResponse]s for the requests | 637 * Returns an [Iterable] of [Future] [HttpResponse]s for the requests |
| 660 * that the [Http] service is currently waiting for. | 638 * that the [Http] service is currently waiting for. |
| 661 */ | 639 */ |
| 662 Iterable<async.Future<HttpResponse> > get pendingRequests => | 640 Iterable<async.Future<HttpResponse> > get pendingRequests => |
| 663 _pendingRequests.values; | 641 _pendingRequests.values; |
| 664 | 642 |
| 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 |
| 665 _buildUrl(String url, Map<String, dynamic> params) { | 710 _buildUrl(String url, Map<String, dynamic> params) { |
| 666 if (params == null) return url; | 711 if (params == null) return url; |
| 667 var parts = []; | 712 var parts = []; |
| 668 | 713 |
| 669 new List.from(params.keys)..sort()..forEach((String key) { | 714 new List.from(params.keys)..sort()..forEach((String key) { |
| 670 var value = params[key]; | 715 var value = params[key]; |
| 671 if (value == null) return; | 716 if (value == null) return; |
| 672 if (value is! List) value = [value]; | 717 if (value is! List) value = [value]; |
| 673 | 718 |
| 674 value.forEach((v) { | 719 value.forEach((v) { |
| 675 if (v is Map) v = JSON.encode(v); | 720 if (v is Map) v = JSON.encode(v); |
| 676 parts.add(_encodeUriQuery(key) + '=' + _encodeUriQuery("$v")); | 721 parts.add(_encodeUriQuery(key) + '=' + _encodeUriQuery("$v")); |
| 677 }); | 722 }); |
| 678 }); | 723 }); |
| 679 return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); | 724 return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); |
| 680 } | 725 } |
| 681 | 726 |
| 682 _encodeUriQuery(val, {bool pctEncodeSpaces: false}) => | 727 _encodeUriQuery(val, {bool pctEncodeSpaces: false}) => |
| 683 Uri.encodeComponent(val) | 728 Uri.encodeComponent(val) |
| 684 .replaceAll('%40', '@') | 729 .replaceAll('%40', '@') |
| 685 .replaceAll('%3A', ':') | 730 .replaceAll('%3A', ':') |
| 686 .replaceAll('%24', r'$') | 731 .replaceAll('%24', r'$') |
| 687 .replaceAll('%2C', ',') | 732 .replaceAll('%2C', ',') |
| 688 .replaceAll('%20', pctEncodeSpaces ? '%20' : '+'); | 733 .replaceAll('%20', pctEncodeSpaces ? '%20' : '+'); |
| 689 } | 734 } |
| OLD | NEW |