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

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

Issue 12758009: Support GZip encoding on the http server. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Add new test Created 7 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
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 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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698