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. |
432 headers.contentLength = 0; | 411 headers.contentLength = 0; |
433 } | 412 } |
434 _writeHeaders(); | 413 _writeHeaders(); |
435 if (!_ignoreBody) { | |
436 if (_chunked) { | |
437 _ChunkedTransformer._addChunk([], _ioSink.writeBytes); | |
438 } | |
439 } | |
440 _ioSink.close(); | 414 _ioSink.close(); |
441 } | 415 } |
442 | 416 |
443 Future<T> get done => _ioSink.done.then((_) => this); | 417 Future<T> get done { |
418 _writeHeaders(); | |
419 return _ioSink.done; | |
420 } | |
444 | 421 |
445 void _writeHeaders() { | 422 void _writeHeaders() { |
446 if (_headersWritten) return; | 423 if (_headersWritten) return; |
447 _headersWritten = true; | 424 _headersWritten = true; |
448 _ioSink.encoding = Encoding.ASCII; | 425 _ioSink.encoding = Encoding.ASCII; |
449 _writeHeader(); | 426 _writeHeader(); |
427 _ioSink = new IOSink(new _HttpOutboundConsumer(_ioSink, _consume)); | |
450 _ioSink.encoding = encoding; | 428 _ioSink.encoding = encoding; |
429 } | |
430 | |
431 Future _consume(IOSink ioSink, Stream<List<int>> stream) { | |
432 int contentLength = headers.contentLength; | |
451 if (_ignoreBody) { | 433 if (_ignoreBody) { |
452 _ioSink.close(); | 434 ioSink.close(); |
453 return; | 435 return stream.reduce(null, (x, y) {}).then((_) => this); |
454 } | 436 } |
455 _chunked = headers.chunkedTransferEncoding; | 437 if (headers.chunkedTransferEncoding) { |
456 if (headers.contentLength >= 0) { | 438 bool isServerSide = this is _HttpResponse; |
457 _outgoing.setTransferLength(headers.contentLength); | 439 if (isServerSide) { |
440 List acceptEncodings = | |
441 _httpRequest.headers[HttpHeaders.ACCEPT_ENCODING]; | |
442 bool canGZip = acceptEncodings != null && acceptEncodings.any( | |
443 (encoding) => encoding.toLowerCase() == "gzip"); | |
Søren Gjesse
2013/03/12 08:54:09
Here the header Content-Encoding should be set to
Anders Johnsen
2013/03/12 10:04:59
Done.
| |
444 if (canGZip && headers.chunkedTransferEncoding) { | |
445 stream = stream.transform(new ZLibDeflater(gzip: true, level: 6)); | |
446 } | |
447 } | |
448 stream = stream.transform(new _ChunkedTransformer()); | |
449 } else if (contentLength >= 0) { | |
450 stream = stream.transform(new _ContentLengthValidator(contentLength)); | |
458 } | 451 } |
452 return stream.pipe(ioSink).then((_) => this); | |
459 } | 453 } |
460 | 454 |
461 void _writeHeader(); // TODO(ajohnsen): Better name. | 455 void _writeHeader(); // TODO(ajohnsen): Better name. |
462 } | 456 } |
463 | 457 |
464 | 458 |
459 class _HttpOutboundConsumer implements StreamConsumer { | |
460 Function _consume; | |
461 IOSink _ioSink; | |
462 _HttpOutboundConsumer(IOSink this._ioSink, Function this._consume); | |
463 | |
464 Future consume(var stream) => _consume(_ioSink, stream); | |
465 } | |
466 | |
467 | |
465 class _HttpResponse extends _HttpOutboundMessage<HttpResponse> | 468 class _HttpResponse extends _HttpOutboundMessage<HttpResponse> |
466 implements HttpResponse { | 469 implements HttpResponse { |
467 int statusCode = 200; | 470 int statusCode = 200; |
468 String _reasonPhrase; | 471 String _reasonPhrase; |
469 List<Cookie> _cookies; | 472 List<Cookie> _cookies; |
470 _HttpRequest _httpRequest; | 473 _HttpRequest _httpRequest; |
471 | 474 |
472 _HttpResponse(String protocolVersion, | 475 _HttpResponse(String protocolVersion, |
473 _HttpOutgoing _outgoing) | 476 _HttpOutgoing _outgoing) |
474 : super(protocolVersion, _outgoing); | 477 : super(protocolVersion, _outgoing); |
(...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
737 | 740 |
738 // Write headers. | 741 // Write headers. |
739 headers._write(_ioSink); | 742 headers._write(_ioSink); |
740 writeCRLF(); | 743 writeCRLF(); |
741 } | 744 } |
742 } | 745 } |
743 | 746 |
744 | 747 |
745 // Transformer that transforms data to HTTP Chunked Encoding. | 748 // Transformer that transforms data to HTTP Chunked Encoding. |
746 class _ChunkedTransformer extends StreamEventTransformer<List<int>, List<int>> { | 749 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) { | 750 void handleData(List<int> data, EventSink<List<int>> sink) { |
751 _addChunk(data, sink.add); | 751 sink.add(_chunkHeader(data.length)); |
752 if (data.length > 0) sink.add(data); | |
753 sink.add(_chunkFooter); | |
752 } | 754 } |
753 | 755 |
754 void handleDone(EventSink<List<int>> sink) { | 756 void handleDone(EventSink<List<int>> sink) { |
755 if (writeEnd) { | 757 handleData([], sink); |
756 _addChunk([], sink.add); | |
757 } | |
758 sink.close(); | 758 sink.close(); |
759 } | 759 } |
760 | 760 |
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) { | 761 static List<int> _chunkHeader(int length) { |
768 const hexDigits = const [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, | 762 const hexDigits = const [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, |
769 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46]; | 763 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46]; |
770 var header = []; | 764 var header = []; |
771 if (length == 0) { | 765 if (length == 0) { |
772 header.add(hexDigits[length]); | 766 header.add(hexDigits[length]); |
773 } else { | 767 } else { |
774 while (length > 0) { | 768 while (length > 0) { |
775 header.insertRange(0, 1, hexDigits[length % 16]); | 769 header.insertRange(0, 1, hexDigits[length % 16]); |
776 length = length >> 4; | 770 length = length >> 4; |
777 } | 771 } |
778 } | 772 } |
779 header.add(_CharCode.CR); | 773 header.add(_CharCode.CR); |
780 header.add(_CharCode.LF); | 774 header.add(_CharCode.LF); |
781 return header; | 775 return header; |
782 } | 776 } |
783 | 777 |
784 // Footer is just a CRLF. | 778 // Footer is just a CRLF. |
785 static List<int> get _chunkFooter => const [_CharCode.CR, _CharCode.LF]; | 779 static List<int> get _chunkFooter => const [_CharCode.CR, _CharCode.LF]; |
786 } | 780 } |
787 | 781 |
788 | 782 |
789 // Transformer that invokes [_onDone] when completed. | 783 // Transformer that validates the content length. |
790 class _DoneTransformer implements StreamTransformer<List<int>, List<int>> { | 784 class _ContentLengthValidator |
791 final StreamController<List<int>> _controller | 785 extends StreamEventTransformer<List<int>, List<int>> { |
792 = new StreamController<List<int>>(); | 786 final int expectedContentLength; |
793 final Function _onDone; | 787 int _bytesWritten = 0; |
794 | 788 |
795 _DoneTransformer(this._onDone); | 789 _ContentLengthValidator(int this.expectedContentLength); |
796 | 790 |
797 Stream<List<int>> bind(Stream<List<int>> stream) { | 791 void handleData(List<int> data, EventSink<List<int>> sink) { |
798 var subscription = stream.listen( | 792 _bytesWritten += data.length; |
799 _controller.add, | 793 if (_bytesWritten > expectedContentLength) { |
800 onError: _controller.addError, | 794 sink.addError(new HttpException( |
801 onDone: () { | 795 "Content size exceeds specified contentLength. " |
802 _onDone(); | 796 "$_bytesWritten bytes written while expected " |
803 _controller.close(); | 797 "$expectedContentLength. " |
804 }); | 798 "[${new String.fromCharCodes(data)}]")); |
805 return _controller.stream; | 799 sink.close(); |
800 } else { | |
801 sink.add(data); | |
802 } | |
803 } | |
804 | |
805 void handleDone(EventSink<List<int>> sink) { | |
806 if (_bytesWritten < expectedContentLength) { | |
807 sink.addError(new HttpException( | |
808 "Content size below specified contentLength. " | |
809 " $_bytesWritten bytes written while expected " | |
810 "$expectedContentLength.")); | |
811 } | |
812 sink.close(); | |
806 } | 813 } |
807 } | 814 } |
808 | 815 |
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 | 816 |
857 // Extends StreamConsumer as this is an internal type, only used to pipe to. | 817 // Extends StreamConsumer as this is an internal type, only used to pipe to. |
858 class _HttpOutgoing implements StreamConsumer<List<int>, dynamic> { | 818 class _HttpOutgoing implements StreamConsumer<List<int>, dynamic> { |
859 final _DataValidatorTransformer _validator = new _DataValidatorTransformer(); | |
860 Function _onStream; | 819 Function _onStream; |
861 final Completer _consumeCompleter = new Completer(); | 820 final Completer _consumeCompleter = new Completer(); |
862 | 821 |
863 Future onStream(Future callback(Stream<List<int>> stream)) { | 822 Future onStream(Future callback(Stream<List<int>> stream)) { |
864 _onStream = callback; | 823 _onStream = callback; |
865 return _consumeCompleter.future; | 824 return _consumeCompleter.future; |
866 } | 825 } |
867 | 826 |
868 void setTransferLength(int transferLength) { | |
869 _validator.expectedTransferLength = transferLength; | |
870 } | |
871 | |
872 Future consume(Stream<List<int>> stream) { | 827 Future consume(Stream<List<int>> stream) { |
873 _onStream(stream.transform(_validator)) | 828 _onStream(stream) |
874 .then((_) => _consumeCompleter.complete(), | 829 .then((_) => _consumeCompleter.complete(), |
875 onError: _consumeCompleter.completeError); | 830 onError: _consumeCompleter.completeError); |
876 // Use .then to ensure a Future branch. | 831 // Use .then to ensure a Future branch. |
877 return _consumeCompleter.future.then((_) => this); | 832 return _consumeCompleter.future.then((_) => this); |
878 } | 833 } |
879 } | 834 } |
880 | 835 |
881 | 836 |
882 class _HttpClientConnection { | 837 class _HttpClientConnection { |
883 final String key; | 838 final String key; |
(...skipping 820 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1704 | 1659 |
1705 | 1660 |
1706 class _RedirectInfo implements RedirectInfo { | 1661 class _RedirectInfo implements RedirectInfo { |
1707 const _RedirectInfo(int this.statusCode, | 1662 const _RedirectInfo(int this.statusCode, |
1708 String this.method, | 1663 String this.method, |
1709 Uri this.location); | 1664 Uri this.location); |
1710 final int statusCode; | 1665 final int statusCode; |
1711 final String method; | 1666 final String method; |
1712 final Uri location; | 1667 final Uri location; |
1713 } | 1668 } |
OLD | NEW |