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 class _HttpIncoming extends Stream<List<int>> { | 7 class _HttpIncoming extends Stream<List<int>> { |
8 final int _transferLength; | 8 final int _transferLength; |
9 final Completer _dataCompleter = new Completer(); | 9 final Completer _dataCompleter = new Completer(); |
10 Stream<List<int>> _stream; | 10 Stream<List<int>> _stream; |
(...skipping 304 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
315 return new Future.immediate(this); | 315 return new Future.immediate(this); |
316 } | 316 } |
317 } | 317 } |
318 | 318 |
319 | 319 |
320 abstract class _HttpOutboundMessage<T> implements IOSink { | 320 abstract class _HttpOutboundMessage<T> implements IOSink { |
321 // Used to mark when the body should be written. This is used for HEAD | 321 // Used to mark when the body should be written. This is used for HEAD |
322 // requests and in error handling. | 322 // requests and in error handling. |
323 bool _ignoreBody = false; | 323 bool _ignoreBody = false; |
324 bool _headersWritten = false; | 324 bool _headersWritten = false; |
325 bool _chunked = false; | |
326 | 325 |
327 final IOSink _ioSink; | 326 IOSink _ioSink; |
328 final _HttpOutgoing _outgoing; | 327 final _HttpOutgoing _outgoing; |
329 | 328 |
330 final _HttpHeaders headers; | 329 final _HttpHeaders headers; |
331 | 330 |
332 _HttpOutboundMessage(String protocolVersion, _HttpOutgoing outgoing) | 331 _HttpOutboundMessage(String protocolVersion, _HttpOutgoing outgoing) |
333 : _outgoing = outgoing, | 332 : _outgoing = outgoing, |
334 _ioSink = new IOSink(outgoing, encoding: Encoding.ASCII), | 333 _ioSink = new IOSink(outgoing, encoding: Encoding.ASCII), |
335 headers = new _HttpHeaders(protocolVersion); | 334 headers = new _HttpHeaders(protocolVersion); |
336 | 335 |
337 int get contentLength => headers.contentLength; | 336 int get contentLength => headers.contentLength; |
338 void set contentLength(int contentLength) { | 337 void set contentLength(int contentLength) { |
339 headers.contentLength = contentLength; | 338 headers.contentLength = contentLength; |
340 } | 339 } |
341 | 340 |
342 bool get persistentConnection => headers.persistentConnection; | 341 bool get persistentConnection => headers.persistentConnection; |
343 void set persistentConnection(bool p) { | 342 void set persistentConnection(bool p) { |
344 headers.persistentConnection = p; | 343 headers.persistentConnection = p; |
345 } | 344 } |
346 | 345 |
347 Encoding get encoding { | 346 Encoding get encoding { |
348 var charset; | 347 var charset; |
349 if (headers.contentType != null && headers.contentType.charset != null) { | 348 if (headers.contentType != null && headers.contentType.charset != null) { |
350 charset = headers.contentType.charset; | 349 charset = headers.contentType.charset; |
351 } else { | 350 } else { |
352 charset = "iso-8859-1"; | 351 charset = "iso-8859-1"; |
353 } | 352 } |
354 return Encoding.fromName(charset); | 353 return Encoding.fromName(charset); |
355 } | 354 } |
356 | 355 |
357 void set encoding(Encoding value) { | 356 void set encoding(Encoding value) { |
358 throw new StateError("IOSink encoding is not mutable"); | 357 throw new StateError("IOSink encoding is not mutable"); |
359 } | 358 } |
360 | 359 |
361 void write(Object obj) { | 360 void write(Object obj) { |
362 _writeHeaders(); | 361 _writeHeaders(); |
363 if (_ignoreBody) return; | |
364 // This comment is copied from runtime/lib/string_buffer_patch.dart. | 362 // This comment is copied from runtime/lib/string_buffer_patch.dart. |
365 // TODO(srdjan): The following four lines could be replaced by | 363 // TODO(srdjan): The following four lines could be replaced by |
366 // '$obj', but apparently this is too slow on the Dart VM. | 364 // '$obj', but apparently this is too slow on the Dart VM. |
367 String string; | 365 String string; |
368 if (obj is String) { | 366 if (obj is String) { |
369 string = obj; | 367 string = obj; |
370 } else { | 368 } else { |
371 string = obj.toString(); | 369 string = obj.toString(); |
372 if (string is! String) { | 370 if (string is! String) { |
373 throw new ArgumentError('toString() did not return a string'); | 371 throw new ArgumentError('toString() did not return a string'); |
374 } | 372 } |
375 } | 373 } |
376 if (string.isEmpty) return; | 374 if (string.isEmpty) return; |
377 if (_chunked) { | 375 _ioSink.write(string); |
378 _ChunkedTransformer._addChunk(_encodeString(string, encoding), | |
379 _ioSink.writeBytes); | |
380 } else { | |
381 _ioSink.write(string); | |
382 } | |
383 } | 376 } |
384 | 377 |
385 void writeAll(Iterable objects) { | 378 void writeAll(Iterable objects) { |
386 for (Object obj in objects) write(obj); | 379 for (Object obj in objects) write(obj); |
387 } | 380 } |
388 | 381 |
389 void writeln(Object obj) { | 382 void writeln(Object obj) { |
390 write(obj); | 383 write(obj); |
391 write("\n"); | 384 write("\n"); |
392 } | 385 } |
393 | 386 |
394 void writeCharCode(int charCode) { | 387 void writeCharCode(int charCode) { |
395 write(new String.fromCharCode(charCode)); | 388 write(new String.fromCharCode(charCode)); |
396 } | 389 } |
397 | 390 |
398 void writeBytes(List<int> data) { | 391 void writeBytes(List<int> data) { |
399 _writeHeaders(); | 392 _writeHeaders(); |
400 if (_ignoreBody || data.length == 0) return; | 393 if (data.length == 0) return; |
401 if (_chunked) { | 394 _ioSink.writeBytes(data); |
402 _ChunkedTransformer._addChunk(data, _ioSink.writeBytes); | |
403 } else { | |
404 _ioSink.writeBytes(data); | |
405 } | |
406 } | 395 } |
407 | 396 |
408 Future<T> consume(Stream<List<int>> stream) { | 397 Future<T> consume(Stream<List<int>> stream) { |
409 _writeHeaders(); | 398 _writeHeaders(); |
410 if (_ignoreBody) return new Future.immediate(this); | 399 return _ioSink.consume(stream); |
411 if (_chunked) { | |
412 // Transform when chunked. | |
413 stream = stream.transform(new _ChunkedTransformer()); | |
414 } | |
415 return _ioSink.consume(stream).then((_) => this); | |
416 } | 400 } |
417 | 401 |
418 Future<T> writeStream(Stream<List<int>> stream) { | 402 Future<T> writeStream(Stream<List<int>> stream) { |
419 _writeHeaders(); | 403 _writeHeaders(); |
420 if (_ignoreBody) return new Future.immediate(this); | |
421 if (_chunked) { | |
422 // Transform when chunked. | |
423 stream = stream.transform(new _ChunkedTransformer(writeEnd: false)); | |
424 } | |
425 return _ioSink.writeStream(stream).then((_) => this); | 404 return _ioSink.writeStream(stream).then((_) => this); |
426 } | 405 } |
427 | 406 |
428 void close() { | 407 void close() { |
429 if (!_headersWritten && !_ignoreBody && headers.chunkedTransferEncoding) { | 408 if (!_headersWritten && !_ignoreBody && headers.chunkedTransferEncoding) { |
430 // If no body was written, _ignoreBody is false (it's not a HEAD | 409 // If no body was written, _ignoreBody is false (it's not a HEAD |
431 // request) and the content-length is unspecified, set contentLength to 0. | 410 // request) and the content-length is unspecified, set contentLength to 0. |
| 411 headers.chunkedTransferEncoding = false; |
432 headers.contentLength = 0; | 412 headers.contentLength = 0; |
433 } | 413 } |
434 _writeHeaders(); | 414 _writeHeaders(); |
435 if (!_ignoreBody) { | |
436 if (_chunked) { | |
437 _ChunkedTransformer._addChunk([], _ioSink.writeBytes); | |
438 } | |
439 } | |
440 _ioSink.close(); | 415 _ioSink.close(); |
441 } | 416 } |
442 | 417 |
443 Future<T> get done => _ioSink.done.then((_) => this); | 418 Future<T> get done { |
| 419 _writeHeaders(); |
| 420 return _ioSink.done; |
| 421 } |
444 | 422 |
445 void _writeHeaders() { | 423 void _writeHeaders() { |
446 if (_headersWritten) return; | 424 if (_headersWritten) return; |
447 _headersWritten = true; | 425 _headersWritten = true; |
448 _ioSink.encoding = Encoding.ASCII; | 426 _ioSink.encoding = Encoding.ASCII; |
| 427 headers._synchronize(); // Be sure the 'chunked' option is updated. |
| 428 bool asGZip = false; |
| 429 bool isServerSide = this is _HttpResponse; |
| 430 if (isServerSide && headers.chunkedTransferEncoding) { |
| 431 List acceptEncodings = |
| 432 _httpRequest.headers[HttpHeaders.ACCEPT_ENCODING]; |
| 433 List contentEncoding = headers[HttpHeaders.CONTENT_ENCODING]; |
| 434 if (acceptEncodings != null && |
| 435 acceptEncodings.any((encoding) => encoding.toLowerCase() == "gzip") && |
| 436 contentEncoding == null) { |
| 437 headers.set(HttpHeaders.CONTENT_ENCODING, "gzip"); |
| 438 asGZip = true; |
| 439 } |
| 440 } |
449 _writeHeader(); | 441 _writeHeader(); |
| 442 _ioSink = new IOSink(new _HttpOutboundConsumer(_ioSink, _consume, asGZip)); |
450 _ioSink.encoding = encoding; | 443 _ioSink.encoding = encoding; |
| 444 } |
| 445 |
| 446 Future _consume(IOSink ioSink, Stream<List<int>> stream, bool asGZip) { |
| 447 int contentLength = headers.contentLength; |
451 if (_ignoreBody) { | 448 if (_ignoreBody) { |
452 _ioSink.close(); | 449 ioSink.close(); |
453 return; | 450 return stream.reduce(null, (x, y) {}).then((_) => this); |
454 } | 451 } |
455 _chunked = headers.chunkedTransferEncoding; | 452 if (headers.chunkedTransferEncoding) { |
456 if (headers.contentLength >= 0) { | 453 if (asGZip) { |
457 _outgoing.setTransferLength(headers.contentLength); | 454 stream = stream.transform(new ZLibDeflater(gzip: true, level: 6)); |
| 455 } |
| 456 stream = stream.transform(new _ChunkedTransformer()); |
| 457 } else if (contentLength >= 0) { |
| 458 stream = stream.transform(new _ContentLengthValidator(contentLength)); |
458 } | 459 } |
| 460 return stream.pipe(ioSink).then((_) => this); |
459 } | 461 } |
460 | 462 |
461 void _writeHeader(); // TODO(ajohnsen): Better name. | 463 void _writeHeader(); // TODO(ajohnsen): Better name. |
462 } | 464 } |
463 | 465 |
464 | 466 |
| 467 class _HttpOutboundConsumer implements StreamConsumer { |
| 468 Function _consume; |
| 469 IOSink _ioSink; |
| 470 bool _asGZip; |
| 471 _HttpOutboundConsumer(IOSink this._ioSink, |
| 472 Function this._consume, |
| 473 bool this._asGZip); |
| 474 |
| 475 Future consume(var stream) => _consume(_ioSink, stream, _asGZip); |
| 476 } |
| 477 |
| 478 |
465 class _HttpResponse extends _HttpOutboundMessage<HttpResponse> | 479 class _HttpResponse extends _HttpOutboundMessage<HttpResponse> |
466 implements HttpResponse { | 480 implements HttpResponse { |
467 int statusCode = 200; | 481 int statusCode = 200; |
468 String _reasonPhrase; | 482 String _reasonPhrase; |
469 List<Cookie> _cookies; | 483 List<Cookie> _cookies; |
470 _HttpRequest _httpRequest; | 484 _HttpRequest _httpRequest; |
471 | 485 |
472 _HttpResponse(String protocolVersion, | 486 _HttpResponse(String protocolVersion, |
473 _HttpOutgoing _outgoing) | 487 _HttpOutgoing _outgoing) |
474 : super(protocolVersion, _outgoing); | 488 : super(protocolVersion, _outgoing); |
(...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
737 | 751 |
738 // Write headers. | 752 // Write headers. |
739 headers._write(_ioSink); | 753 headers._write(_ioSink); |
740 writeCRLF(); | 754 writeCRLF(); |
741 } | 755 } |
742 } | 756 } |
743 | 757 |
744 | 758 |
745 // Transformer that transforms data to HTTP Chunked Encoding. | 759 // Transformer that transforms data to HTTP Chunked Encoding. |
746 class _ChunkedTransformer extends StreamEventTransformer<List<int>, List<int>> { | 760 class _ChunkedTransformer extends StreamEventTransformer<List<int>, List<int>> { |
747 final bool writeEnd; | |
748 _ChunkedTransformer({this.writeEnd: true}); | |
749 | |
750 void handleData(List<int> data, EventSink<List<int>> sink) { | 761 void handleData(List<int> data, EventSink<List<int>> sink) { |
751 _addChunk(data, sink.add); | 762 sink.add(_chunkHeader(data.length)); |
| 763 if (data.length > 0) sink.add(data); |
| 764 sink.add(_chunkFooter); |
752 } | 765 } |
753 | 766 |
754 void handleDone(EventSink<List<int>> sink) { | 767 void handleDone(EventSink<List<int>> sink) { |
755 if (writeEnd) { | 768 handleData([], sink); |
756 _addChunk([], sink.add); | |
757 } | |
758 sink.close(); | 769 sink.close(); |
759 } | 770 } |
760 | 771 |
761 static void _addChunk(List<int> data, void add(List<int> data)) { | |
762 add(_chunkHeader(data.length)); | |
763 if (data.length > 0) add(data); | |
764 add(_chunkFooter); | |
765 } | |
766 | |
767 static List<int> _chunkHeader(int length) { | 772 static List<int> _chunkHeader(int length) { |
768 const hexDigits = const [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, | 773 const hexDigits = const [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, |
769 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46]; | 774 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46]; |
770 var header = []; | 775 var header = []; |
771 if (length == 0) { | 776 if (length == 0) { |
772 header.add(hexDigits[length]); | 777 header.add(hexDigits[length]); |
773 } else { | 778 } else { |
774 while (length > 0) { | 779 while (length > 0) { |
775 header.insertRange(0, 1, hexDigits[length % 16]); | 780 header.insertRange(0, 1, hexDigits[length % 16]); |
776 length = length >> 4; | 781 length = length >> 4; |
777 } | 782 } |
778 } | 783 } |
779 header.add(_CharCode.CR); | 784 header.add(_CharCode.CR); |
780 header.add(_CharCode.LF); | 785 header.add(_CharCode.LF); |
781 return header; | 786 return header; |
782 } | 787 } |
783 | 788 |
784 // Footer is just a CRLF. | 789 // Footer is just a CRLF. |
785 static List<int> get _chunkFooter => const [_CharCode.CR, _CharCode.LF]; | 790 static List<int> get _chunkFooter => const [_CharCode.CR, _CharCode.LF]; |
786 } | 791 } |
787 | 792 |
788 | 793 |
789 // Transformer that invokes [_onDone] when completed. | 794 // Transformer that validates the content length. |
790 class _DoneTransformer implements StreamTransformer<List<int>, List<int>> { | 795 class _ContentLengthValidator |
791 final StreamController<List<int>> _controller | 796 extends StreamEventTransformer<List<int>, List<int>> { |
792 = new StreamController<List<int>>(); | 797 final int expectedContentLength; |
793 final Function _onDone; | 798 int _bytesWritten = 0; |
794 | 799 |
795 _DoneTransformer(this._onDone); | 800 _ContentLengthValidator(int this.expectedContentLength); |
796 | 801 |
797 Stream<List<int>> bind(Stream<List<int>> stream) { | 802 void handleData(List<int> data, EventSink<List<int>> sink) { |
798 var subscription = stream.listen( | 803 _bytesWritten += data.length; |
799 _controller.add, | 804 if (_bytesWritten > expectedContentLength) { |
800 onError: _controller.addError, | 805 sink.addError(new AsyncError(new HttpException( |
801 onDone: () { | 806 "Content size exceeds specified contentLength. " |
802 _onDone(); | 807 "$_bytesWritten bytes written while expected " |
803 _controller.close(); | 808 "$expectedContentLength. " |
804 }); | 809 "[${new String.fromCharCodes(data)}]"))); |
805 return _controller.stream; | 810 sink.close(); |
| 811 } else { |
| 812 sink.add(data); |
| 813 } |
| 814 } |
| 815 |
| 816 void handleDone(EventSink<List<int>> sink) { |
| 817 if (_bytesWritten < expectedContentLength) { |
| 818 sink.addError(new AsyncError(new HttpException( |
| 819 "Content size below specified contentLength. " |
| 820 " $_bytesWritten bytes written while expected " |
| 821 "$expectedContentLength."))); |
| 822 } |
| 823 sink.close(); |
806 } | 824 } |
807 } | 825 } |
808 | 826 |
809 // Transformer that validates the data written. | |
810 class _DataValidatorTransformer | |
811 implements StreamTransformer<List<int>, List<int>> { | |
812 final StreamController<List<int>> _controller = | |
813 new StreamController<List<int>>(); | |
814 int _bytesWritten = 0; | |
815 | |
816 int expectedTransferLength; | |
817 | |
818 Stream<List<int>> bind(Stream<List<int>> stream) { | |
819 var subscription; | |
820 subscription = stream.listen( | |
821 (data) { | |
822 if (expectedTransferLength != null) { | |
823 _bytesWritten += data.length; | |
824 if (_bytesWritten > expectedTransferLength) { | |
825 subscription.cancel(); | |
826 _controller.addError(new HttpException( | |
827 "Content size exceeds specified contentLength. " | |
828 "$_bytesWritten bytes written while expected " | |
829 "$expectedTransferLength. " | |
830 "[${new String.fromCharCodes(data)}]")); | |
831 _controller.close(); | |
832 return; | |
833 } | |
834 } | |
835 _controller.add(data); | |
836 }, | |
837 onError: (error) { | |
838 _controller.addError(error); | |
839 _controller.close(); | |
840 }, | |
841 onDone: () { | |
842 if (expectedTransferLength != null) { | |
843 if (_bytesWritten < expectedTransferLength) { | |
844 _controller.addError(new HttpException( | |
845 "Content size below specified contentLength. " | |
846 " $_bytesWritten bytes written while expected " | |
847 "$expectedTransferLength.")); | |
848 } | |
849 } | |
850 _controller.close(); | |
851 }, | |
852 unsubscribeOnError: true); | |
853 return _controller.stream; | |
854 } | |
855 } | |
856 | 827 |
857 // Extends StreamConsumer as this is an internal type, only used to pipe to. | 828 // Extends StreamConsumer as this is an internal type, only used to pipe to. |
858 class _HttpOutgoing implements StreamConsumer<List<int>, dynamic> { | 829 class _HttpOutgoing implements StreamConsumer<List<int>, dynamic> { |
859 final _DataValidatorTransformer _validator = new _DataValidatorTransformer(); | |
860 Function _onStream; | 830 Function _onStream; |
861 final Completer _consumeCompleter = new Completer(); | 831 final Completer _consumeCompleter = new Completer(); |
862 | 832 |
863 Future onStream(Future callback(Stream<List<int>> stream)) { | 833 Future onStream(Future callback(Stream<List<int>> stream)) { |
864 _onStream = callback; | 834 _onStream = callback; |
865 return _consumeCompleter.future; | 835 return _consumeCompleter.future; |
866 } | 836 } |
867 | 837 |
868 void setTransferLength(int transferLength) { | |
869 _validator.expectedTransferLength = transferLength; | |
870 } | |
871 | |
872 Future consume(Stream<List<int>> stream) { | 838 Future consume(Stream<List<int>> stream) { |
873 _onStream(stream.transform(_validator)) | 839 _onStream(stream) |
874 .then((_) => _consumeCompleter.complete(), | 840 .then((_) => _consumeCompleter.complete(), |
875 onError: _consumeCompleter.completeError); | 841 onError: _consumeCompleter.completeError); |
876 // Use .then to ensure a Future branch. | 842 // Use .then to ensure a Future branch. |
877 return _consumeCompleter.future.then((_) => this); | 843 return _consumeCompleter.future.then((_) => this); |
878 } | 844 } |
879 } | 845 } |
880 | 846 |
881 | 847 |
882 class _HttpClientConnection { | 848 class _HttpClientConnection { |
883 final String key; | 849 final String key; |
(...skipping 820 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1704 | 1670 |
1705 | 1671 |
1706 class _RedirectInfo implements RedirectInfo { | 1672 class _RedirectInfo implements RedirectInfo { |
1707 const _RedirectInfo(int this.statusCode, | 1673 const _RedirectInfo(int this.statusCode, |
1708 String this.method, | 1674 String this.method, |
1709 Uri this.location); | 1675 Uri this.location); |
1710 final int statusCode; | 1676 final int statusCode; |
1711 final String method; | 1677 final String method; |
1712 final Uri location; | 1678 final Uri location; |
1713 } | 1679 } |
OLD | NEW |