| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 part of dart.io; | 5 part of dart.io; |
| 6 | 6 |
| 7 const int _OUTGOING_BUFFER_SIZE = 8 * 1024; | 7 const int _OUTGOING_BUFFER_SIZE = 8 * 1024; |
| 8 | 8 |
| 9 class _HttpIncoming extends Stream<List<int>> { | 9 class _HttpIncoming extends Stream<List<int>> { |
| 10 final int _transferLength; | 10 final int _transferLength; |
| (...skipping 391 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 402 return this; | 402 return this; |
| 403 } | 403 } |
| 404 }); | 404 }); |
| 405 } | 405 } |
| 406 } | 406 } |
| 407 | 407 |
| 408 | 408 |
| 409 abstract class _HttpOutboundMessage<T> extends _IOSinkImpl { | 409 abstract class _HttpOutboundMessage<T> extends _IOSinkImpl { |
| 410 // Used to mark when the body should be written. This is used for HEAD | 410 // Used to mark when the body should be written. This is used for HEAD |
| 411 // requests and in error handling. | 411 // requests and in error handling. |
| 412 bool _ignoreBody = false; | |
| 413 bool _headersWritten = false; | |
| 414 bool _encodingSet = false; | 412 bool _encodingSet = false; |
| 415 | 413 |
| 416 final Uri _uri; | 414 final Uri _uri; |
| 417 final _HttpOutgoing _outgoing; | 415 final _HttpOutgoing _outgoing; |
| 418 | 416 |
| 419 final _HttpHeaders headers; | 417 final _HttpHeaders headers; |
| 420 | 418 |
| 421 _HttpOutboundMessage(this._uri, | 419 _HttpOutboundMessage(this._uri, |
| 422 String protocolVersion, | 420 String protocolVersion, |
| 423 this._outgoing) | 421 _HttpOutgoing outgoing) |
| 424 : super(new _HttpOutboundConsumer(), null), | 422 : super(outgoing, null), |
| 425 headers = new _HttpHeaders(protocolVersion) { | 423 headers = new _HttpHeaders(protocolVersion), |
| 424 _outgoing = outgoing { |
| 426 _outgoing.outbound = this; | 425 _outgoing.outbound = this; |
| 427 (_target as _HttpOutboundConsumer).outbound = this; | |
| 428 _encodingMutable = false; | 426 _encodingMutable = false; |
| 429 } | 427 } |
| 430 | 428 |
| 431 int get contentLength => headers.contentLength; | 429 int get contentLength => headers.contentLength; |
| 432 void set contentLength(int contentLength) { | 430 void set contentLength(int contentLength) { |
| 433 headers.contentLength = contentLength; | 431 headers.contentLength = contentLength; |
| 434 } | 432 } |
| 435 | 433 |
| 436 bool get persistentConnection => headers.persistentConnection; | 434 bool get persistentConnection => headers.persistentConnection; |
| 437 void set persistentConnection(bool p) { | 435 void set persistentConnection(bool p) { |
| 438 headers.persistentConnection = p; | 436 headers.persistentConnection = p; |
| 439 } | 437 } |
| 440 | 438 |
| 441 Encoding get encoding { | 439 Encoding get encoding { |
| 442 if (_encodingSet && _headersWritten) { | 440 if (_encodingSet && _outgoing.headersWritten) { |
| 443 return _encoding; | 441 return _encoding; |
| 444 } | 442 } |
| 445 var charset; | 443 var charset; |
| 446 if (headers.contentType != null && headers.contentType.charset != null) { | 444 if (headers.contentType != null && headers.contentType.charset != null) { |
| 447 charset = headers.contentType.charset; | 445 charset = headers.contentType.charset; |
| 448 } else { | 446 } else { |
| 449 charset = "iso-8859-1"; | 447 charset = "iso-8859-1"; |
| 450 } | 448 } |
| 451 return Encoding.getByName(charset); | 449 return Encoding.getByName(charset); |
| 452 } | 450 } |
| 453 | 451 |
| 454 void add(List<int> data) { | 452 void add(List<int> data) { |
| 455 if (data.length == 0) return; | 453 if (data.length == 0) return; |
| 456 super.add(data); | 454 super.add(data); |
| 457 } | 455 } |
| 458 | 456 |
| 459 void write(Object obj) { | 457 void write(Object obj) { |
| 460 if (!_encodingSet) { | 458 if (!_encodingSet) { |
| 461 _encoding = encoding; | 459 _encoding = encoding; |
| 462 _encodingSet = true; | 460 _encodingSet = true; |
| 463 } | 461 } |
| 464 super.write(obj); | 462 super.write(obj); |
| 465 } | 463 } |
| 466 | 464 |
| 467 Future _writeHeaders({bool drainRequest: true, | |
| 468 bool setOutgoing: true}) { | |
| 469 // TODO(ajohnsen): Avoid excessive futures in this method. | |
| 470 write() { | |
| 471 try { | |
| 472 _writeHeader(); | |
| 473 } catch (_) { | |
| 474 // Headers too large. | |
| 475 throw new HttpException( | |
| 476 "Headers size exceeded the of '$_OUTGOING_BUFFER_SIZE'" | |
| 477 " bytes"); | |
| 478 } | |
| 479 return this; | |
| 480 } | |
| 481 if (_headersWritten) return new Future.value(this); | |
| 482 _headersWritten = true; | |
| 483 Future drainFuture; | |
| 484 bool isServerSide = this is _HttpResponse; | |
| 485 bool gzip = false; | |
| 486 if (isServerSide) { | |
| 487 var response = this; | |
| 488 if (headers.chunkedTransferEncoding) { | |
| 489 List acceptEncodings = | |
| 490 response._httpRequest.headers[HttpHeaders.ACCEPT_ENCODING]; | |
| 491 List contentEncoding = headers[HttpHeaders.CONTENT_ENCODING]; | |
| 492 if (acceptEncodings != null && | |
| 493 acceptEncodings | |
| 494 .expand((list) => list.split(",")) | |
| 495 .any((encoding) => encoding.trim().toLowerCase() == "gzip") && | |
| 496 contentEncoding == null) { | |
| 497 headers.set(HttpHeaders.CONTENT_ENCODING, "gzip"); | |
| 498 gzip = true; | |
| 499 } | |
| 500 } | |
| 501 if (drainRequest && !response._httpRequest._incoming.hasSubscriber) { | |
| 502 drainFuture = response._httpRequest.drain().catchError((_) {}); | |
| 503 } | |
| 504 } else { | |
| 505 drainRequest = false; | |
| 506 } | |
| 507 if (_ignoreBody) { | |
| 508 return new Future.sync(write).then((_) => _outgoing.close()); | |
| 509 } | |
| 510 if (setOutgoing) { | |
| 511 int contentLength = headers.contentLength; | |
| 512 if (headers.chunkedTransferEncoding) { | |
| 513 _outgoing.chunked = true; | |
| 514 if (gzip) _outgoing.gzip = true; | |
| 515 } else if (contentLength >= 0) { | |
| 516 _outgoing.contentLength = contentLength; | |
| 517 } | |
| 518 } | |
| 519 if (drainFuture != null) { | |
| 520 return drainFuture.then((_) => write()); | |
| 521 } | |
| 522 return new Future.sync(write); | |
| 523 } | |
| 524 | |
| 525 Future _addStream(Stream<List<int>> stream) { | |
| 526 // TODO(ajohnsen): Merge into _HttpOutgoing. | |
| 527 if (_ignoreBody) { | |
| 528 stream.drain().catchError((_) {}); | |
| 529 return _writeHeaders(); | |
| 530 } | |
| 531 if (_headersWritten) { | |
| 532 return _outgoing.addStream(stream); | |
| 533 } else { | |
| 534 var completer = new Completer.sync(); | |
| 535 var future = _outgoing.addStream(stream, completer.future); | |
| 536 _writeHeaders().then(completer.complete); | |
| 537 return future; | |
| 538 } | |
| 539 } | |
| 540 | |
| 541 Future _close() { | |
| 542 // TODO(ajohnsen): Merge into _HttpOutgoing. | |
| 543 if (!_headersWritten) { | |
| 544 if (!_ignoreBody && headers.contentLength == -1) { | |
| 545 // If no body was written, _ignoreBody is false (it's not a HEAD | |
| 546 // request) and the content-length is unspecified, set contentLength to | |
| 547 // 0. | |
| 548 headers.chunkedTransferEncoding = false; | |
| 549 headers.contentLength = 0; | |
| 550 } else if (!_ignoreBody && headers.contentLength > 0) { | |
| 551 return _outgoing.addStream( | |
| 552 new Stream.fromFuture(new Future.error(new HttpException( | |
| 553 "No content even though contentLength was specified to be " | |
| 554 "greater than 0: ${headers.contentLength}.", | |
| 555 uri: _uri)))); | |
| 556 } | |
| 557 } | |
| 558 return _writeHeaders().whenComplete(_outgoing.close); | |
| 559 } | |
| 560 | |
| 561 void _writeHeader(); | 465 void _writeHeader(); |
| 562 } | 466 } |
| 563 | 467 |
| 564 | 468 |
| 565 class _HttpOutboundConsumer implements StreamConsumer { | |
| 566 // TODO(ajohnsen): Once _addStream and _close is merged into _HttpOutgoing, | |
| 567 // this class can be removed. | |
| 568 _HttpOutboundMessage outbound; | |
| 569 _HttpOutboundConsumer(); | |
| 570 | |
| 571 Future addStream(var stream) => outbound._addStream(stream); | |
| 572 Future close() => outbound._close(); | |
| 573 } | |
| 574 | |
| 575 | |
| 576 class _HttpResponse extends _HttpOutboundMessage<HttpResponse> | 469 class _HttpResponse extends _HttpOutboundMessage<HttpResponse> |
| 577 implements HttpResponse { | 470 implements HttpResponse { |
| 578 int _statusCode = 200; | 471 int _statusCode = 200; |
| 579 String _reasonPhrase; | 472 String _reasonPhrase; |
| 580 List<Cookie> _cookies; | 473 List<Cookie> _cookies; |
| 581 _HttpRequest _httpRequest; | 474 _HttpRequest _httpRequest; |
| 582 Duration _deadline; | 475 Duration _deadline; |
| 583 Timer _deadlineTimer; | 476 Timer _deadlineTimer; |
| 584 | 477 |
| 585 _HttpResponse(Uri uri, | 478 _HttpResponse(Uri uri, |
| 586 String protocolVersion, | 479 String protocolVersion, |
| 587 _HttpOutgoing outgoing, | 480 _HttpOutgoing outgoing, |
| 588 String serverHeader) | 481 String serverHeader) |
| 589 : super(uri, protocolVersion, outgoing) { | 482 : super(uri, protocolVersion, outgoing) { |
| 590 if (serverHeader != null) headers._add('server', serverHeader); | 483 if (serverHeader != null) headers._add('server', serverHeader); |
| 591 } | 484 } |
| 592 | 485 |
| 593 List<Cookie> get cookies { | 486 List<Cookie> get cookies { |
| 594 if (_cookies == null) _cookies = new List<Cookie>(); | 487 if (_cookies == null) _cookies = new List<Cookie>(); |
| 595 return _cookies; | 488 return _cookies; |
| 596 } | 489 } |
| 597 | 490 |
| 598 int get statusCode => _statusCode; | 491 int get statusCode => _statusCode; |
| 599 void set statusCode(int statusCode) { | 492 void set statusCode(int statusCode) { |
| 600 if (_headersWritten) throw new StateError("Header already sent"); | 493 if (_outgoing.headersWritten) throw new StateError("Header already sent"); |
| 601 _statusCode = statusCode; | 494 _statusCode = statusCode; |
| 602 } | 495 } |
| 603 | 496 |
| 604 String get reasonPhrase => _findReasonPhrase(statusCode); | 497 String get reasonPhrase => _findReasonPhrase(statusCode); |
| 605 void set reasonPhrase(String reasonPhrase) { | 498 void set reasonPhrase(String reasonPhrase) { |
| 606 if (_headersWritten) throw new StateError("Header already sent"); | 499 if (_outgoing.headersWritten) throw new StateError("Header already sent"); |
| 607 _reasonPhrase = reasonPhrase; | 500 _reasonPhrase = reasonPhrase; |
| 608 } | 501 } |
| 609 | 502 |
| 610 Future redirect(Uri location, {int status: HttpStatus.MOVED_TEMPORARILY}) { | 503 Future redirect(Uri location, {int status: HttpStatus.MOVED_TEMPORARILY}) { |
| 611 if (_headersWritten) throw new StateError("Header already sent"); | 504 if (_outgoing.headersWritten) throw new StateError("Header already sent"); |
| 612 statusCode = status; | 505 statusCode = status; |
| 613 headers.set("location", location.toString()); | 506 headers.set("location", location.toString()); |
| 614 return close(); | 507 return close(); |
| 615 } | 508 } |
| 616 | 509 |
| 617 Future<Socket> detachSocket() { | 510 Future<Socket> detachSocket() { |
| 618 if (_headersWritten) throw new StateError("Headers already sent"); | 511 if (_outgoing.headersWritten) throw new StateError("Headers already sent"); |
| 619 deadline = null; // Be sure to stop any deadline. | 512 deadline = null; // Be sure to stop any deadline. |
| 620 var future = _httpRequest._httpConnection.detachSocket(); | 513 var future = _httpRequest._httpConnection.detachSocket(); |
| 621 _writeHeaders(drainRequest: false, | 514 var headersFuture = _outgoing.writeHeaders(drainRequest: false, |
| 622 setOutgoing: false).then((_) => close()); | 515 setOutgoing: false); |
| 516 assert(headersFuture == null); |
| 623 // Close connection so the socket is 'free'. | 517 // Close connection so the socket is 'free'. |
| 624 close(); | 518 close(); |
| 625 done.catchError((_) { | 519 done.catchError((_) { |
| 626 // Catch any error on done, as they automatically will be | 520 // Catch any error on done, as they automatically will be |
| 627 // propagated to the websocket. | 521 // propagated to the websocket. |
| 628 }); | 522 }); |
| 629 return future; | 523 return future; |
| 630 } | 524 } |
| 631 | 525 |
| 632 HttpConnectionInfo get connectionInfo => _httpRequest.connectionInfo; | 526 HttpConnectionInfo get connectionInfo => _httpRequest.connectionInfo; |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 809 return _response; | 703 return _response; |
| 810 } | 704 } |
| 811 | 705 |
| 812 Future<HttpClientResponse> close() { | 706 Future<HttpClientResponse> close() { |
| 813 super.close(); | 707 super.close(); |
| 814 return done; | 708 return done; |
| 815 } | 709 } |
| 816 | 710 |
| 817 int get maxRedirects => _maxRedirects; | 711 int get maxRedirects => _maxRedirects; |
| 818 void set maxRedirects(int maxRedirects) { | 712 void set maxRedirects(int maxRedirects) { |
| 819 if (_headersWritten) throw new StateError("Request already sent"); | 713 if (_outgoing.headersWritten) throw new StateError("Request already sent"); |
| 820 _maxRedirects = maxRedirects; | 714 _maxRedirects = maxRedirects; |
| 821 } | 715 } |
| 822 | 716 |
| 823 bool get followRedirects => _followRedirects; | 717 bool get followRedirects => _followRedirects; |
| 824 void set followRedirects(bool followRedirects) { | 718 void set followRedirects(bool followRedirects) { |
| 825 if (_headersWritten) throw new StateError("Request already sent"); | 719 if (_outgoing.headersWritten) throw new StateError("Request already sent"); |
| 826 _followRedirects = followRedirects; | 720 _followRedirects = followRedirects; |
| 827 } | 721 } |
| 828 | 722 |
| 829 HttpConnectionInfo get connectionInfo => _httpClientConnection.connectionInfo; | 723 HttpConnectionInfo get connectionInfo => _httpClientConnection.connectionInfo; |
| 830 | 724 |
| 831 void _onIncoming(_HttpIncoming incoming) { | 725 void _onIncoming(_HttpIncoming incoming) { |
| 832 var response = new _HttpClientResponse(incoming, this, _httpClient); | 726 var response = new _HttpClientResponse(incoming, this, _httpClient); |
| 833 Future<HttpClientResponse> future; | 727 Future<HttpClientResponse> future; |
| 834 if (followRedirects && response.isRedirect) { | 728 if (followRedirects && response.isRedirect) { |
| 835 if (response.redirects.length < maxRedirects) { | 729 if (response.redirects.length < maxRedirects) { |
| (...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 954 | 848 |
| 955 | 849 |
| 956 // The _HttpOutgoing handles all of the following: | 850 // The _HttpOutgoing handles all of the following: |
| 957 // - Buffering | 851 // - Buffering |
| 958 // - GZip compressionm | 852 // - GZip compressionm |
| 959 // - Content-Length validation. | 853 // - Content-Length validation. |
| 960 // - Errors. | 854 // - Errors. |
| 961 // | 855 // |
| 962 // Most notable is the GZip compression, that uses a double-buffering system, | 856 // Most notable is the GZip compression, that uses a double-buffering system, |
| 963 // one before gzip (_gzipBuffer) and one after (_buffer). | 857 // one before gzip (_gzipBuffer) and one after (_buffer). |
| 964 class _HttpOutgoing | 858 class _HttpOutgoing implements StreamConsumer<List<int>> { |
| 965 implements StreamConsumer<List<int>> { | |
| 966 static const List<int> _footerAndChunk0Length = | 859 static const List<int> _footerAndChunk0Length = |
| 967 const [_CharCode.CR, _CharCode.LF, 0x30, _CharCode.CR, _CharCode.LF, | 860 const [_CharCode.CR, _CharCode.LF, 0x30, _CharCode.CR, _CharCode.LF, |
| 968 _CharCode.CR, _CharCode.LF]; | 861 _CharCode.CR, _CharCode.LF]; |
| 969 | 862 |
| 970 static const List<int> _chunk0Length = | 863 static const List<int> _chunk0Length = |
| 971 const [0x30, _CharCode.CR, _CharCode.LF, _CharCode.CR, _CharCode.LF]; | 864 const [0x30, _CharCode.CR, _CharCode.LF, _CharCode.CR, _CharCode.LF]; |
| 972 | 865 |
| 973 final Completer _doneCompleter = new Completer(); | 866 final Completer _doneCompleter = new Completer(); |
| 974 final Socket socket; | 867 final Socket socket; |
| 975 | 868 |
| 869 bool ignoreBody = false; |
| 870 bool headersWritten = false; |
| 871 |
| 976 Uint8List _buffer; | 872 Uint8List _buffer; |
| 977 int _length = 0; | 873 int _length = 0; |
| 978 | 874 |
| 979 Future _closeFuture; | 875 Future _closeFuture; |
| 980 | 876 |
| 981 bool chunked = false; | 877 bool chunked = false; |
| 982 int _pendingChunkedFooter = 0; | 878 int _pendingChunkedFooter = 0; |
| 983 | 879 |
| 984 int contentLength; | 880 int contentLength; |
| 985 int _bytesWritten = 0; | 881 int _bytesWritten = 0; |
| 986 | 882 |
| 987 bool _gzip = false; | 883 bool _gzip = false; |
| 988 ByteConversionSink _gzipSink; | 884 ByteConversionSink _gzipSink; |
| 989 // _gzipAdd is set iff the sink is being added to. It's used to specify where | 885 // _gzipAdd is set iff the sink is being added to. It's used to specify where |
| 990 // gzipped data should be taken (sometimes a controller, sometimes a socket). | 886 // gzipped data should be taken (sometimes a controller, sometimes a socket). |
| 991 Function _gzipAdd; | 887 Function _gzipAdd; |
| 992 Uint8List _gzipBuffer; | 888 Uint8List _gzipBuffer; |
| 993 int _gzipBufferLength = 0; | 889 int _gzipBufferLength = 0; |
| 994 | 890 |
| 995 bool _socketError = false; | 891 bool _socketError = false; |
| 996 | 892 |
| 997 _HttpOutboundMessage outbound; | 893 _HttpOutboundMessage outbound; |
| 998 | 894 |
| 999 bool _ignoreError(error) | |
| 1000 => (error is SocketException || error is TlsException) && | |
| 1001 outbound is HttpResponse; | |
| 1002 | |
| 1003 _HttpOutgoing(this.socket); | 895 _HttpOutgoing(this.socket); |
| 1004 | 896 |
| 1005 Future addStream(Stream<List<int>> stream, [Future pauseFuture]) { | 897 // Returns either a future or 'null', if it was able to write headers |
| 898 // immediately. |
| 899 Future writeHeaders({bool drainRequest: true, bool setOutgoing: true}) { |
| 900 Future write() { |
| 901 try { |
| 902 outbound._writeHeader(); |
| 903 } catch (_) { |
| 904 // Headers too large. |
| 905 return new Future.error(new HttpException( |
| 906 "Headers size exceeded the of '$_OUTGOING_BUFFER_SIZE'" |
| 907 " bytes")); |
| 908 } |
| 909 } |
| 910 if (headersWritten) return null; |
| 911 headersWritten = true; |
| 912 Future drainFuture; |
| 913 bool isServerSide = outbound is _HttpResponse; |
| 914 bool gzip = false; |
| 915 if (isServerSide) { |
| 916 var response = outbound; |
| 917 if (outbound.headers.chunkedTransferEncoding) { |
| 918 List acceptEncodings = |
| 919 response._httpRequest.headers[HttpHeaders.ACCEPT_ENCODING]; |
| 920 List contentEncoding = outbound.headers[HttpHeaders.CONTENT_ENCODING]; |
| 921 if (acceptEncodings != null && |
| 922 acceptEncodings |
| 923 .expand((list) => list.split(",")) |
| 924 .any((encoding) => encoding.trim().toLowerCase() == "gzip") && |
| 925 contentEncoding == null) { |
| 926 outbound.headers.set(HttpHeaders.CONTENT_ENCODING, "gzip"); |
| 927 gzip = true; |
| 928 } |
| 929 } |
| 930 if (drainRequest && !response._httpRequest._incoming.hasSubscriber) { |
| 931 drainFuture = response._httpRequest.drain().catchError((_) {}); |
| 932 } |
| 933 } else { |
| 934 drainRequest = false; |
| 935 } |
| 936 if (ignoreBody) { |
| 937 return write(); |
| 938 } |
| 939 if (setOutgoing) { |
| 940 int contentLength = outbound.headers.contentLength; |
| 941 if (outbound.headers.chunkedTransferEncoding) { |
| 942 chunked = true; |
| 943 if (gzip) this.gzip = true; |
| 944 } else if (contentLength >= 0) { |
| 945 this.contentLength = contentLength; |
| 946 } |
| 947 } |
| 948 if (drainFuture != null) { |
| 949 return drainFuture.then((_) => write()); |
| 950 } |
| 951 return write(); |
| 952 } |
| 953 |
| 954 |
| 955 Future addStream(Stream<List<int>> stream) { |
| 1006 if (_socketError) { | 956 if (_socketError) { |
| 1007 stream.listen(null).cancel(); | 957 stream.listen(null).cancel(); |
| 1008 return new Future.value(outbound); | 958 return new Future.value(outbound); |
| 1009 } | 959 } |
| 960 if (ignoreBody) { |
| 961 stream.drain().catchError((_) {}); |
| 962 var future = writeHeaders(); |
| 963 if (future != null) { |
| 964 return future.then((_) => close()); |
| 965 } |
| 966 return close(); |
| 967 } |
| 1010 var sub; | 968 var sub; |
| 1011 var controller; | |
| 1012 // Use new stream so we are able to pause (see below listen). The | 969 // Use new stream so we are able to pause (see below listen). The |
| 1013 // alternative is to use stream.extand, but that won't give us a way of | 970 // alternative is to use stream.extand, but that won't give us a way of |
| 1014 // pausing. | 971 // pausing. |
| 1015 controller = new StreamController( | 972 var controller = new StreamController( |
| 1016 onPause: () => sub.pause(), | 973 onPause: () => sub.pause(), |
| 1017 onResume: () => sub.resume(), | 974 onResume: () => sub.resume(), |
| 1018 sync: true); | 975 sync: true); |
| 1019 | 976 |
| 1020 void onData(data) { | 977 void onData(data) { |
| 1021 if (_socketError) return; | 978 if (_socketError) return; |
| 1022 if (data.length == 0) return; | 979 if (data.length == 0) return; |
| 1023 if (chunked) { | 980 if (chunked) { |
| 1024 if (_gzip) { | 981 if (_gzip) { |
| 1025 _gzipAdd = controller.add; | 982 _gzipAdd = controller.add; |
| (...skipping 17 matching lines...) Expand all Loading... |
| 1043 } | 1000 } |
| 1044 } | 1001 } |
| 1045 _addChunk(data, controller.add); | 1002 _addChunk(data, controller.add); |
| 1046 } | 1003 } |
| 1047 | 1004 |
| 1048 sub = stream.listen( | 1005 sub = stream.listen( |
| 1049 onData, | 1006 onData, |
| 1050 onError: controller.addError, | 1007 onError: controller.addError, |
| 1051 onDone: controller.close, | 1008 onDone: controller.close, |
| 1052 cancelOnError: true); | 1009 cancelOnError: true); |
| 1053 | 1010 // Write headers now that we are listening to the stream. |
| 1054 // While incoming is being drained, the pauseFuture is non-null. Pause | 1011 if (!headersWritten) { |
| 1055 // output until it's drained. | 1012 var future = writeHeaders(); |
| 1056 if (pauseFuture != null) { | 1013 if (future != null) { |
| 1057 sub.pause(pauseFuture); | 1014 // While incoming is being drained, the pauseFuture is non-null. Pause |
| 1015 // output until it's drained. |
| 1016 sub.pause(future); |
| 1017 } |
| 1058 } | 1018 } |
| 1059 | |
| 1060 return socket.addStream(controller.stream) | 1019 return socket.addStream(controller.stream) |
| 1061 .then((_) { | 1020 .then((_) { |
| 1062 return outbound; | 1021 return outbound; |
| 1063 }, onError: (error) { | 1022 }, onError: (error) { |
| 1064 // Be sure to close it in case of an error. | 1023 // Be sure to close it in case of an error. |
| 1065 if (_gzip) _gzipSink.close(); | 1024 if (_gzip) _gzipSink.close(); |
| 1066 _socketError = true; | 1025 _socketError = true; |
| 1067 _doneCompleter.completeError(error); | 1026 _doneCompleter.completeError(error); |
| 1068 if (_ignoreError(error)) { | 1027 if (_ignoreError(error)) { |
| 1069 return outbound; | 1028 return outbound; |
| 1070 } else { | 1029 } else { |
| 1071 throw error; | 1030 throw error; |
| 1072 } | 1031 } |
| 1073 }); | 1032 }); |
| 1074 } | 1033 } |
| 1075 | 1034 |
| 1076 Future close() { | 1035 Future close() { |
| 1077 // If we are already closed, return that future. | 1036 // If we are already closed, return that future. |
| 1078 if (_closeFuture != null) return _closeFuture; | 1037 if (_closeFuture != null) return _closeFuture; |
| 1079 // If we earlier saw an error, return immidiate. The notification to | 1038 // If we earlier saw an error, return immediate. The notification to |
| 1080 // _Http*Connection is already done. | 1039 // _Http*Connection is already done. |
| 1081 if (_socketError) return new Future.value(outbound); | 1040 if (_socketError) return new Future.value(outbound); |
| 1041 if (!headersWritten && !ignoreBody) { |
| 1042 if (outbound.headers.contentLength == -1) { |
| 1043 // If no body was written, ignoreBody is false (it's not a HEAD |
| 1044 // request) and the content-length is unspecified, set contentLength to |
| 1045 // 0. |
| 1046 outbound.headers.chunkedTransferEncoding = false; |
| 1047 outbound.headers.contentLength = 0; |
| 1048 } else if (outbound.headers.contentLength > 0) { |
| 1049 var error = new HttpException( |
| 1050 "No content even though contentLength was specified to be " |
| 1051 "greater than 0: ${outbound.headers.contentLength}.", |
| 1052 uri: outbound._uri); |
| 1053 _doneCompleter.completeError(error); |
| 1054 return _closeFuture = new Future.error(error); |
| 1055 } |
| 1056 } |
| 1082 // If contentLength was specified, validate it. | 1057 // If contentLength was specified, validate it. |
| 1083 if (contentLength != null) { | 1058 if (contentLength != null) { |
| 1084 if (_bytesWritten < contentLength) { | 1059 if (_bytesWritten < contentLength) { |
| 1085 var error = new HttpException( | 1060 var error = new HttpException( |
| 1086 "Content size below specified contentLength. " | 1061 "Content size below specified contentLength. " |
| 1087 " $_bytesWritten bytes written but expected " | 1062 " $_bytesWritten bytes written but expected " |
| 1088 "$contentLength."); | 1063 "$contentLength.", |
| 1064 uri: outbound._uri); |
| 1089 _doneCompleter.completeError(error); | 1065 _doneCompleter.completeError(error); |
| 1090 return _closeFuture = new Future.error(error); | 1066 return _closeFuture = new Future.error(error); |
| 1091 } | 1067 } |
| 1092 } | 1068 } |
| 1093 // In case of chunked encoding (and gzip), handle remaining gzip data and | 1069 |
| 1094 // append the 'footer' for chunked encoding. | 1070 Future finalize() { |
| 1095 if (chunked) { | 1071 // In case of chunked encoding (and gzip), handle remaining gzip data and |
| 1096 if (_gzip) { | 1072 // append the 'footer' for chunked encoding. |
| 1097 _gzipAdd = socket.add; | 1073 if (chunked) { |
| 1098 if (_gzipBufferLength > 0) { | 1074 if (_gzip) { |
| 1099 _gzipSink.add(new Uint8List.view( | 1075 _gzipAdd = socket.add; |
| 1100 _gzipBuffer.buffer, 0, _gzipBufferLength)); | 1076 if (_gzipBufferLength > 0) { |
| 1077 _gzipSink.add(new Uint8List.view( |
| 1078 _gzipBuffer.buffer, 0, _gzipBufferLength)); |
| 1079 } |
| 1080 _gzipBuffer = null; |
| 1081 _gzipSink.close(); |
| 1082 _gzipAdd = null; |
| 1101 } | 1083 } |
| 1102 _gzipBuffer = null; | 1084 _addChunk(_chunkHeader(0), socket.add); |
| 1103 _gzipSink.close(); | |
| 1104 _gzipAdd = null; | |
| 1105 } | 1085 } |
| 1106 _addChunk(_chunkHeader(0), socket.add); | 1086 // Add any remaining data in the buffer. |
| 1087 if (_length > 0) { |
| 1088 socket.add(new Uint8List.view(_buffer.buffer, 0, _length)); |
| 1089 } |
| 1090 // Clear references, for better GC. |
| 1091 _buffer = null; |
| 1092 // And finally flush it. As we support keep-alive, never close it from |
| 1093 // here. Once the socket is flushed, we'll be able to reuse it (signaled |
| 1094 // by the 'done' future). |
| 1095 return socket.flush() |
| 1096 .then((_) { |
| 1097 _doneCompleter.complete(socket); |
| 1098 return outbound; |
| 1099 }, onError: (error) { |
| 1100 _doneCompleter.completeError(error); |
| 1101 if (_ignoreError(error)) { |
| 1102 return outbound; |
| 1103 } else { |
| 1104 throw error; |
| 1105 } |
| 1106 }); |
| 1107 } | 1107 } |
| 1108 // Add any remaining data in the buffer. | 1108 |
| 1109 if (_length > 0) { | 1109 var future = writeHeaders(); |
| 1110 socket.add(new Uint8List.view(_buffer.buffer, 0, _length)); | 1110 if (future != null) { |
| 1111 return _closeFuture = future.whenComplete(finalize); |
| 1111 } | 1112 } |
| 1112 // Clear references, for better GC. | 1113 return _closeFuture = finalize(); |
| 1113 _buffer = null; | |
| 1114 // And finally flush it. As we support keep-alive, never close it from here. | |
| 1115 // Once the socket is flushed, we'll be able to reuse it (signaled by the | |
| 1116 // 'done' future). | |
| 1117 return _closeFuture = socket.flush() | |
| 1118 .then((_) { | |
| 1119 _doneCompleter.complete(socket); | |
| 1120 return outbound; | |
| 1121 }, onError: (error) { | |
| 1122 _doneCompleter.completeError(error); | |
| 1123 if (_ignoreError(error)) { | |
| 1124 return outbound; | |
| 1125 } else { | |
| 1126 throw error; | |
| 1127 } | |
| 1128 }); | |
| 1129 } | 1114 } |
| 1130 | 1115 |
| 1131 Future get done => _doneCompleter.future; | 1116 Future get done => _doneCompleter.future; |
| 1132 | 1117 |
| 1133 void setHeader(List<int> data, int length) { | 1118 void setHeader(List<int> data, int length) { |
| 1134 assert(_length == 0); | 1119 assert(_length == 0); |
| 1135 assert(data.length == _OUTGOING_BUFFER_SIZE); | 1120 assert(data.length == _OUTGOING_BUFFER_SIZE); |
| 1136 _buffer = data; | 1121 _buffer = data; |
| 1137 _length = length; | 1122 _length = length; |
| 1138 } | 1123 } |
| 1139 | 1124 |
| 1140 void set gzip(bool value) { | 1125 void set gzip(bool value) { |
| 1141 _gzip = value; | 1126 _gzip = value; |
| 1142 if (_gzip) { | 1127 if (_gzip) { |
| 1143 _gzipBuffer = new Uint8List(_OUTGOING_BUFFER_SIZE); | 1128 _gzipBuffer = new Uint8List(_OUTGOING_BUFFER_SIZE); |
| 1144 assert(_gzipSink == null); | 1129 assert(_gzipSink == null); |
| 1145 _gzipSink = new ZLibEncoder(gzip: true) | 1130 _gzipSink = new ZLibEncoder(gzip: true) |
| 1146 .startChunkedConversion( | 1131 .startChunkedConversion( |
| 1147 new _HttpGZipSink((data) { | 1132 new _HttpGZipSink((data) { |
| 1148 // We are closing down prematurely, due to an error. Discard. | 1133 // We are closing down prematurely, due to an error. Discard. |
| 1149 if (_gzipAdd == null) return; | 1134 if (_gzipAdd == null) return; |
| 1150 _addChunk(_chunkHeader(data.length), _gzipAdd); | 1135 _addChunk(_chunkHeader(data.length), _gzipAdd); |
| 1151 _pendingChunkedFooter = 2; | 1136 _pendingChunkedFooter = 2; |
| 1152 _addChunk(data, _gzipAdd); | 1137 _addChunk(data, _gzipAdd); |
| 1153 })); | 1138 })); |
| 1154 } | 1139 } |
| 1155 } | 1140 } |
| 1156 | 1141 |
| 1142 bool _ignoreError(error) |
| 1143 => (error is SocketException || error is TlsException) && |
| 1144 outbound is HttpResponse; |
| 1145 |
| 1157 void _addGZipChunk(chunk, void add(List<int> data)) { | 1146 void _addGZipChunk(chunk, void add(List<int> data)) { |
| 1158 if (chunk.length > _gzipBuffer.length - _gzipBufferLength) { | 1147 if (chunk.length > _gzipBuffer.length - _gzipBufferLength) { |
| 1159 add(new Uint8List.view( | 1148 add(new Uint8List.view( |
| 1160 _gzipBuffer.buffer, 0, _gzipBufferLength)); | 1149 _gzipBuffer.buffer, 0, _gzipBufferLength)); |
| 1161 _gzipBuffer = new Uint8List(_OUTGOING_BUFFER_SIZE); | 1150 _gzipBuffer = new Uint8List(_OUTGOING_BUFFER_SIZE); |
| 1162 _gzipBufferLength = 0; | 1151 _gzipBufferLength = 0; |
| 1163 } | 1152 } |
| 1164 if (chunk.length > _OUTGOING_BUFFER_SIZE) { | 1153 if (chunk.length > _OUTGOING_BUFFER_SIZE) { |
| 1165 add(chunk); | 1154 add(chunk); |
| 1166 } else { | 1155 } else { |
| (...skipping 760 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1927 // request is now processed. | 1916 // request is now processed. |
| 1928 _subscription.resume(); | 1917 _subscription.resume(); |
| 1929 } else { | 1918 } else { |
| 1930 // Close socket, keep-alive not used or body sent before | 1919 // Close socket, keep-alive not used or body sent before |
| 1931 // received data was handled. | 1920 // received data was handled. |
| 1932 destroy(); | 1921 destroy(); |
| 1933 } | 1922 } |
| 1934 }, onError: (_) { | 1923 }, onError: (_) { |
| 1935 destroy(); | 1924 destroy(); |
| 1936 }); | 1925 }); |
| 1937 response._ignoreBody = request.method == "HEAD"; | 1926 outgoing.ignoreBody = request.method == "HEAD"; |
| 1938 response._httpRequest = request; | 1927 response._httpRequest = request; |
| 1939 _httpServer._handleRequest(request); | 1928 _httpServer._handleRequest(request); |
| 1940 }, | 1929 }, |
| 1941 onDone: () { | 1930 onDone: () { |
| 1942 destroy(); | 1931 destroy(); |
| 1943 }, | 1932 }, |
| 1944 onError: (error) { | 1933 onError: (error) { |
| 1945 // Ignore failed requests that was closed before headers was received. | 1934 // Ignore failed requests that was closed before headers was received. |
| 1946 destroy(); | 1935 destroy(); |
| 1947 }); | 1936 }); |
| (...skipping 635 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2583 const _RedirectInfo(this.statusCode, this.method, this.location); | 2572 const _RedirectInfo(this.statusCode, this.method, this.location); |
| 2584 } | 2573 } |
| 2585 | 2574 |
| 2586 String _getHttpVersion() { | 2575 String _getHttpVersion() { |
| 2587 var version = Platform.version; | 2576 var version = Platform.version; |
| 2588 // Only include major and minor version numbers. | 2577 // Only include major and minor version numbers. |
| 2589 int index = version.indexOf('.', version.indexOf('.') + 1); | 2578 int index = version.indexOf('.', version.indexOf('.') + 1); |
| 2590 version = version.substring(0, index); | 2579 version = version.substring(0, index); |
| 2591 return 'Dart/$version (dart:io)'; | 2580 return 'Dart/$version (dart:io)'; |
| 2592 } | 2581 } |
| OLD | NEW |