Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: sdk/lib/io/http_impl.dart

Issue 187443004: Avoid unnecessary future in the HTTP stack, after latest cleanup. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tests/standalone/io/http_head_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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 // immidiately..
Søren Gjesse 2014/03/05 11:30:29 immidiately.. .> immediately.
Anders Johnsen 2014/03/05 11:42:54 Done.
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
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 immidiate. 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
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
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 }
OLDNEW
« no previous file with comments | « no previous file | tests/standalone/io/http_head_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698