| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 | 5 |
| 6 class _HttpIncomingConnection extends StreamController<List<int>> { | 6 class _HttpIncomingConnection extends StreamController<List<int>> { |
| 7 final Function _pause; | 7 final Function _pause; |
| 8 final Function _resume; | 8 final Function _resume; |
| 9 bool _ignore = false; | 9 bool _ignore = false; |
| 10 final SignalCompleter _dataCompleter = new SignalCompleter(); | 10 final SignalCompleter _dataCompleter = new SignalCompleter(); |
| 11 final SignalCompleter _messageCompleter = new SignalCompleter(); | 11 final SignalCompleter _messageCompleter = new SignalCompleter(); |
| 12 | 12 |
| 13 // Common properties. | 13 // Common properties. |
| 14 _HttpHeaders headers; | 14 final _HttpHeaders headers; |
| 15 int contentLength; | |
| 16 bool upgraded = false; | 15 bool upgraded = false; |
| 17 | 16 |
| 18 // ClientResponse properties. | 17 // ClientResponse properties. |
| 19 int statusCode; | 18 int statusCode; |
| 20 String reasonPhrase; | 19 String reasonPhrase; |
| 21 | 20 |
| 22 // Request properties. | 21 // Request properties. |
| 23 String method; | 22 String method; |
| 24 Uri uri; | 23 Uri uri; |
| 25 | 24 |
| 26 _HttpIncomingConnection(void this._pause(), | 25 _HttpIncomingConnection(_HttpHeaders this.headers, |
| 26 void this._pause(), |
| 27 void this._resume()) | 27 void this._resume()) |
| 28 : super.singleSubscription() { | 28 : super.singleSubscription() { |
| 29 _pause(); | 29 _pause(); |
| 30 } | 30 } |
| 31 | 31 |
| 32 // Is completed once all data have been received. | 32 // Is completed once all data have been received. |
| 33 Signal get dataDone => _dataCompleter.signal; | 33 Signal get dataDone => _dataCompleter.signal; |
| 34 | 34 |
| 35 // Is completed once the message have been handled. | 35 // Is completed once the message have been handled. |
| 36 Signal get messageHandled => _messageCompleter.signal; | 36 Signal get messageHandled => _messageCompleter.signal; |
| 37 | 37 |
| 38 void onPauseStateChange() { | 38 void onPauseStateChange() { |
| 39 if (isPaused) { | 39 if (isPaused) { |
| 40 _pause(); | 40 _pause(); |
| 41 } else { | 41 } else { |
| 42 _reasume(); | 42 _resume(); |
| 43 } | 43 } |
| 44 } | 44 } |
| 45 | 45 |
| 46 void onSubscriptionStateChange() { | 46 void onSubscriptionStateChange() { |
| 47 if (hasSubscribers) { | 47 if (hasSubscribers) { |
| 48 _resume(); | 48 _resume(); |
| 49 } else { | 49 } else { |
| 50 _ignore = true; | 50 _ignore = true; |
| 51 } | 51 } |
| 52 } | 52 } |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 109 return _queryParameters; | 109 return _queryParameters; |
| 110 } | 110 } |
| 111 | 111 |
| 112 Uri get uri => _incomingConnection.uri; | 112 Uri get uri => _incomingConnection.uri; |
| 113 | 113 |
| 114 String get method => _incomingConnection.method; | 114 String get method => _incomingConnection.method; |
| 115 } | 115 } |
| 116 | 116 |
| 117 | 117 |
| 118 class _HttpClientResponse extends _HttpIncoming implements HttpClientResponse { | 118 class _HttpClientResponse extends _HttpIncoming implements HttpClientResponse { |
| 119 _HttpClientResponse(_HttpIncomingConnection _incomingConnection) | 119 List<RedirectInfo> get redirects => _httpRequest._responseRedirects; |
| 120 |
| 121 // The HttpClient this response belongs to. |
| 122 final _HttpClient _httpClient; |
| 123 |
| 124 // The HttpClientRequest of this response. |
| 125 final _HttpClientRequest _httpRequest; |
| 126 |
| 127 _HttpClientResponse(_HttpIncomingConnection _incomingConnection, |
| 128 _HttpClientRequest this._httpRequest, |
| 129 _HttpClient this._httpClient) |
| 120 : super(_incomingConnection); | 130 : super(_incomingConnection); |
| 121 | 131 |
| 122 int get statusCode => _incomingConnection.statusCode; | 132 int get statusCode => _incomingConnection.statusCode; |
| 123 String get reasonPhrase => _incomingConnection.reasonPhrase; | 133 String get reasonPhrase => _incomingConnection.reasonPhrase; |
| 124 | 134 |
| 135 bool get isRedirect { |
| 136 if (_httpRequest.method == "GET" || _httpRequest.method == "HEAD") { |
| 137 return statusCode == HttpStatus.MOVED_PERMANENTLY || |
| 138 statusCode == HttpStatus.FOUND || |
| 139 statusCode == HttpStatus.SEE_OTHER || |
| 140 statusCode == HttpStatus.TEMPORARY_REDIRECT; |
| 141 } else if (_httpRequest.method == "POST") { |
| 142 return statusCode == HttpStatus.SEE_OTHER; |
| 143 } |
| 144 return false; |
| 145 } |
| 146 |
| 147 Future<HttpClientResponse> redirect([String method, |
| 148 Uri url, |
| 149 bool followLoops]) { |
| 150 if (method == null) { |
| 151 // Set method as defined by RFC 2616 section 10.3.4. |
| 152 if (statusCode == HttpStatus.SEE_OTHER && _httpRequest.method == "POST") { |
| 153 method = "GET"; |
| 154 } else { |
| 155 method = _httpRequest.method; |
| 156 } |
| 157 } |
| 158 if (url == null) { |
| 159 String location = headers.value(HttpHeaders.LOCATION); |
| 160 if (location == null) { |
| 161 throw new StateError("Response has no Location header for redirect"); |
| 162 } |
| 163 url = new Uri.fromString(location); |
| 164 } |
| 165 if (followLoops != true) { |
| 166 for (var redirect in redirects) { |
| 167 if (redirect.location == url) { |
| 168 return new Future.immediateError( |
| 169 new RedirectLoopException(redirects)); |
| 170 } |
| 171 } |
| 172 } |
| 173 return _httpClient.openUrl(method, url) |
| 174 .then((request) { |
| 175 // Only follow redirects if initial request did. |
| 176 request.followRedirects = _httpRequest.followRedirects; |
| 177 // Allow the same number of redirects. |
| 178 request.maxRedirects = _httpRequest.maxRedirects; |
| 179 // Copy headers. |
| 180 for (var header in _httpRequest.headers._headers.keys) { |
| 181 if (header != HttpHeaders.HOST.toLowerCase()) { |
| 182 request.headers.set(header, _httpRequest.headers[header]); |
| 183 } |
| 184 } |
| 185 request.headers.contentLength = 0; |
| 186 request._responseRedirects.addAll(this.redirects); |
| 187 request._responseRedirects.add(new _RedirectInfo(statusCode, |
| 188 method, |
| 189 url)); |
| 190 return request.close(); |
| 191 }); |
| 192 } |
| 193 |
| 125 StreamSubscription<List<int>> listen(void onData(List<int> event), | 194 StreamSubscription<List<int>> listen(void onData(List<int> event), |
| 126 {void onError(AsyncError error), | 195 {void onError(AsyncError error), |
| 127 void onDone(), | 196 void onDone(), |
| 128 bool unsubscribeOnError}) { | 197 bool unsubscribeOnError}) { |
| 129 return _incomingConnection.listen( | 198 return _incomingConnection.listen( |
| 130 onData, | 199 onData, |
| 131 onError: onError, | 200 onError: onError, |
| 132 onDone: onDone, | 201 onDone: onDone, |
| 133 unsubscribeOnError: unsubscribeOnError); | 202 unsubscribeOnError: unsubscribeOnError); |
| 134 } | 203 } |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 223 | 292 |
| 224 // Write headers. | 293 // Write headers. |
| 225 headers._write(this); | 294 headers._write(this); |
| 226 writeCRLF(); | 295 writeCRLF(); |
| 227 } | 296 } |
| 228 } | 297 } |
| 229 | 298 |
| 230 | 299 |
| 231 class _HttpClientRequest extends _HttpOutgoing<HttpClientRequest> | 300 class _HttpClientRequest extends _HttpOutgoing<HttpClientRequest> |
| 232 implements HttpClientRequest { | 301 implements HttpClientRequest { |
| 233 _HttpClientRequest(Uri this.uri, | 302 final String method; |
| 303 final Uri uri; |
| 304 final List<Cookie> cookies = new List<Cookie>(); |
| 305 |
| 306 // The HttpClient this request belongs to. |
| 307 final _HttpClient _httpClient; |
| 308 |
| 309 final Completer<HttpClientResponse> _responseCompleter |
| 310 = new Completer<HttpClientResponse>(); |
| 311 |
| 312 |
| 313 // TODO(ajohnsen): Get default value from client? |
| 314 bool _followRedirects = true; |
| 315 |
| 316 int _maxRedirects = 5; |
| 317 |
| 318 List<RedirectInfo> _responseRedirects = []; |
| 319 |
| 320 _HttpClientRequest(_HttpOutgoingConnection outgoing, |
| 321 Uri this.uri, |
| 234 String this.method, | 322 String this.method, |
| 235 _HttpOutgoingConnection outgoing) | 323 _HttpClient this._httpClient) |
| 236 : super("1.1", outgoing) { | 324 : super("1.1", outgoing) { |
| 237 // GET and HEAD have 'content-length: 0' by default. | 325 // GET and HEAD have 'content-length: 0' by default. |
| 238 if (method == "GET" || method == "HEAD") { | 326 if (method == "GET" || method == "HEAD") { |
| 239 contentLength = 0; | 327 contentLength = 0; |
| 240 } | 328 } |
| 241 } | 329 } |
| 242 | 330 |
| 331 Future<HttpClientResponse> get response => _responseCompleter.future; |
| 332 |
| 243 Future<HttpClientResponse> close() { | 333 Future<HttpClientResponse> close() { |
| 244 super.close(); | 334 super.close(); |
| 245 return response; | 335 return response; |
| 246 } | 336 } |
| 247 | 337 |
| 338 int get maxRedirects => _maxRedirects; |
| 339 void set maxRedirects(int maxRedirects) { |
| 340 if (_headersWritten) throw new StateError("Request already sent"); |
| 341 _maxRedirects = maxRedirects; |
| 342 } |
| 343 |
| 344 bool get followRedirects => _followRedirects; |
| 345 void set followRedirects(bool followRedirects) { |
| 346 if (_headersWritten) throw new StateError("Request already sent"); |
| 347 _followRedirects = followRedirects; |
| 348 } |
| 349 |
| 248 void _onIncoming(_HttpIncomingConnection incoming) { | 350 void _onIncoming(_HttpIncomingConnection incoming) { |
| 249 // TODO(ajohnsen): Handle redirect and auth. | 351 // TODO(ajohnsen): Handle auth. |
| 250 _responseCompleter.complete(new _HttpClientResponse(incoming)); | 352 var response = new _HttpClientResponse(incoming, |
| 353 this, |
| 354 _httpClient); |
| 355 |
| 356 Future<HttpClientResponse> future; |
| 357 |
| 358 if (followRedirects && |
| 359 response.isRedirect) { |
| 360 if (response.redirects.length < maxRedirects) { |
| 361 // Redirect |
| 362 future = response.redirect(); |
| 363 } else { |
| 364 // End with exception, too many redirects. |
| 365 future = new Future.immediateError( |
| 366 new RedirectLimitExceededException(response.redirects)); |
| 367 } |
| 368 } else { |
| 369 future = new Future<HttpClientResponse>.immediate(response); |
| 370 } |
| 371 |
| 372 future.then( |
| 373 _responseCompleter.complete, |
| 374 onError: (e) { |
| 375 _responseCompleter.completeError(e.error, e.stackTrace); |
| 376 }); |
| 251 } | 377 } |
| 252 | 378 |
| 253 void _onError(AsyncError error) { | 379 void _onError(AsyncError error) { |
| 254 _responseCompleter.completeError(error.error, error.stackTrace); | 380 _responseCompleter.completeError(error.error, error.stackTrace); |
| 255 } | 381 } |
| 256 | 382 |
| 257 void _writeHeader() { | 383 void _writeHeader() { |
| 258 writeSP() => add([_CharCode.SP]); | 384 writeSP() => add([_CharCode.SP]); |
| 259 writeCRLF() => add([_CharCode.CR, _CharCode.LF]); | 385 writeCRLF() => add([_CharCode.CR, _CharCode.LF]); |
| 260 | 386 |
| (...skipping 30 matching lines...) Expand all Loading... |
| 291 } | 417 } |
| 292 headers.add("cookie", sb.toString()); | 418 headers.add("cookie", sb.toString()); |
| 293 } | 419 } |
| 294 | 420 |
| 295 headers._finalize(); | 421 headers._finalize(); |
| 296 | 422 |
| 297 // Write headers. | 423 // Write headers. |
| 298 headers._write(this); | 424 headers._write(this); |
| 299 writeCRLF(); | 425 writeCRLF(); |
| 300 } | 426 } |
| 301 | |
| 302 Future<HttpClientResponse> get response => _responseCompleter.future; | |
| 303 | |
| 304 final String method; | |
| 305 final Uri uri; | |
| 306 final List<Cookie> cookies = new List<Cookie>(); | |
| 307 final Completer<HttpClientResponse> _responseCompleter | |
| 308 = new Completer<HttpClientResponse>(); | |
| 309 } | 427 } |
| 310 | 428 |
| 311 | 429 |
| 312 // Transformer that transforms data to HTTP Chunked Encoding. | 430 // Transformer that transforms data to HTTP Chunked Encoding. |
| 313 class _ChunkedTransformer extends StreamController<List<int>> | 431 class _ChunkedTransformer extends StreamController<List<int>> |
| 314 implements StreamTransformer<List<int>, List<int>> { | 432 implements StreamTransformer<List<int>, List<int>> { |
| 315 _ChunkedTransformer() : super.singleSubscription(); | 433 _ChunkedTransformer() : super.singleSubscription(); |
| 316 | 434 |
| 317 Stream<List<int>> bind(Stream<List<int>> stream) { | 435 Stream<List<int>> bind(Stream<List<int>> stream) { |
| 318 var subscription = stream.listen( | 436 var subscription = stream.listen( |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 453 class _HttpClient implements HttpClient { | 571 class _HttpClient implements HttpClient { |
| 454 // TODO(ajohnsen): Use eviction timeout. | 572 // TODO(ajohnsen): Use eviction timeout. |
| 455 static const int DEFAULT_EVICTION_TIMEOUT = 60000; | 573 static const int DEFAULT_EVICTION_TIMEOUT = 60000; |
| 456 | 574 |
| 457 Future<HttpClientRequest> open(String method, | 575 Future<HttpClientRequest> open(String method, |
| 458 String host, | 576 String host, |
| 459 int port, | 577 int port, |
| 460 String path) { | 578 String path) { |
| 461 // TODO(sgjesse): The path set here can contain both query and | 579 // TODO(sgjesse): The path set here can contain both query and |
| 462 // fragment. They should be cracked and set correctly. | 580 // fragment. They should be cracked and set correctly. |
| 463 return _open(method, new Uri.fromComponents( | 581 return _openUrl(method, new Uri.fromComponents( |
| 464 scheme: "http", domain: host, port: port, path: path)); | 582 scheme: "http", domain: host, port: port, path: path)); |
| 465 } | 583 } |
| 466 | 584 |
| 467 Future<HttpClientRequest> openUrl(String method, Uri url) { | 585 Future<HttpClientRequest> openUrl(String method, Uri url) { |
| 468 return _openUrl(method, url); | 586 return _openUrl(method, url); |
| 469 } | 587 } |
| 470 | 588 |
| 471 Future<HttpClientRequest> get(String host, | 589 Future<HttpClientRequest> get(String host, |
| 472 int port, | 590 int port, |
| 473 String path) { | 591 String path) { |
| 474 return open("get", host, port, path); | 592 return open("get", host, port, path); |
| 475 } | 593 } |
| 476 | 594 |
| 477 Future<HttpClientRequest> getUrl(Uri url) { | 595 Future<HttpClientRequest> getUrl(Uri url) { |
| 478 return _open("get", url); | 596 return _openUrl("get", url); |
| 479 } | 597 } |
| 480 | 598 |
| 481 Future<HttpClientRequest> post(String host, | 599 Future<HttpClientRequest> post(String host, |
| 482 int port, | 600 int port, |
| 483 String path) { | 601 String path) { |
| 484 return open("post", host, port, path); | 602 return open("post", host, port, path); |
| 485 } | 603 } |
| 486 | 604 |
| 487 Future<HttpClientRequest> postUrl(Uri url) { | 605 Future<HttpClientRequest> postUrl(Uri url) { |
| 488 return _open("post", url); | 606 return _openUrl("post", url); |
| 489 } | 607 } |
| 490 | 608 |
| 491 void close() { | 609 void close() { |
| 492 for (var queue in _idleConnections.values) { | 610 for (var queue in _idleConnections.values) { |
| 493 for (var connection in queue) { | 611 for (var connection in queue) { |
| 494 connection.destroy(); | 612 connection.destroy(); |
| 495 } | 613 } |
| 496 } | 614 } |
| 497 _idleConnections.clear(); | 615 _idleConnections.clear(); |
| 498 for (var connection in _activeConnections) { | 616 for (var connection in _activeConnections) { |
| 499 connection.destroy(); | 617 connection.destroy(); |
| 500 } | 618 } |
| 501 _activeConnections.clear(); | 619 _activeConnections.clear(); |
| 502 } | 620 } |
| 503 | 621 |
| 504 Future<HttpClientRequest> _open(String method, | 622 Future<HttpClientRequest> _openUrl(String method, |
| 505 Uri uri, | 623 Uri uri, |
| 506 [_HttpClientConnection connection]) { | 624 [_HttpClientConnection connection]) { |
| 507 if (method == null) { | 625 if (method == null) { |
| 508 throw new ArgumentError(method); | 626 throw new ArgumentError(method); |
| 509 } | 627 } |
| 510 // TODO(ajohnsen): Handle HTTPS. | 628 // TODO(ajohnsen): Handle HTTPS. |
| 511 if (uri.domain.isEmpty || uri.scheme != "http") { | 629 if (uri.domain.isEmpty || uri.scheme != "http") { |
| 512 throw new ArgumentError("Unsupported scheme '${uri.scheme}' in $uri"); | 630 throw new ArgumentError("Unsupported scheme '${uri.scheme}' in $uri"); |
| 513 } | 631 } |
| 514 | 632 |
| 515 // TODO(ajohnsen): Proxy? | 633 // TODO(ajohnsen): Proxy? |
| 516 Future future; | 634 Future future; |
| 635 int port = uri.port; |
| 636 if (port == 0) port = HttpClient.DEFAULT_HTTP_PORT; |
| 517 if (connection == null) { | 637 if (connection == null) { |
| 518 int port = uri.port; | |
| 519 if (port == 0) port = HttpClient.DEFAULT_HTTP_PORT; | |
| 520 future = _getConnection(uri.domain, port); | 638 future = _getConnection(uri.domain, port); |
| 521 } else { | 639 } else { |
| 522 future = new Future.immediate(connection); | 640 future = new Future.immediate(connection); |
| 523 } | 641 } |
| 524 | 642 |
| 525 return future.then((_HttpClientConnection connection) { | 643 return future.then((_HttpClientConnection connection) { |
| 526 onDone() { | 644 onDone() { |
| 527 // Called when the connection request/response sequence has ended. | 645 // Called when the connection request/response sequence has ended. |
| 528 _returnConnection(connection); | 646 _returnConnection(connection); |
| 529 } | 647 } |
| 530 // Create new internal outgoing connection. | 648 // Create new internal outgoing connection. |
| 531 var outgoing = new _HttpOutgoingConnection(); | 649 var outgoing = new _HttpOutgoingConnection(); |
| 532 // Create new request object, wrapping the outgoing connection. | 650 // Create new request object, wrapping the outgoing connection. |
| 533 var request = new _HttpClientRequest( | 651 var request = new _HttpClientRequest(outgoing, |
| 534 uri, method.toUpperCase(), outgoing); | 652 uri, |
| 653 method.toUpperCase(), |
| 654 this); |
| 655 request.headers.host = uri.domain; |
| 656 request.headers.port = port; |
| 535 // Start sending the request (lazy, delayed until the user provides | 657 // Start sending the request (lazy, delayed until the user provides |
| 536 // data). | 658 // data). |
| 537 connection.sendRequest(outgoing, onDone) | 659 connection.sendRequest(outgoing, onDone) |
| 538 .then((incoming) { | 660 .then((incoming) { |
| 539 // The full request have been sent and a response is received | 661 // The full request have been sent and a response is received |
| 540 // containing status-code, headers and etc. | 662 // containing status-code, headers and etc. |
| 541 request._onIncoming(incoming); | 663 request._onIncoming(incoming); |
| 542 }) | 664 }) |
| 543 .catchError((error) { | 665 .catchError((error) { |
| 544 // An error occoured before the http-header was parsed. This | 666 // An error occoured before the http-header was parsed. This |
| (...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 908 | 1030 |
| 909 | 1031 |
| 910 class _RedirectInfo implements RedirectInfo { | 1032 class _RedirectInfo implements RedirectInfo { |
| 911 const _RedirectInfo(int this.statusCode, | 1033 const _RedirectInfo(int this.statusCode, |
| 912 String this.method, | 1034 String this.method, |
| 913 Uri this.location); | 1035 Uri this.location); |
| 914 final int statusCode; | 1036 final int statusCode; |
| 915 final String method; | 1037 final String method; |
| 916 final Uri location; | 1038 final Uri location; |
| 917 } | 1039 } |
| OLD | NEW |