| OLD | NEW |
| (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 } |
| OLD | NEW |