Fix consolidate HTTP response and add test (#17082) * Fix consolidate response and add test * Update AUTHORS * ignore content-length * fix formatting/update comment * more commas * Fix consolidate response and add test * Update AUTHORS * ignore content-length * fix formatting/update comment * more commas * remove extra newline
diff --git a/AUTHORS b/AUTHORS index 26cc1e9..fd1df22 100644 --- a/AUTHORS +++ b/AUTHORS
@@ -21,3 +21,4 @@ Fredrik Simón <fredrik@fsimon.net> Ali Bitek <alibitek@protonmail.ch> Tetsuhiro Ueda <najeira@gmail.com> +Dan Field <dfield@gmail.com>
diff --git a/packages/flutter/lib/src/foundation/consolidate_response.dart b/packages/flutter/lib/src/foundation/consolidate_response.dart index 183117e..cd6f158 100644 --- a/packages/flutter/lib/src/foundation/consolidate_response.dart +++ b/packages/flutter/lib/src/foundation/consolidate_response.dart
@@ -10,39 +10,24 @@ /// /// The future returned will forward all errors emitted by [response]. Future<Uint8List> consolidateHttpClientResponseBytes(HttpClientResponse response) { - // dart:io guarantees that [contentLength] is -1 if the the header is missing or - // invalid. This could still happen if a mocked response object does not fully - // implement the interface. - assert(response.contentLength != null); + // response.contentLength is not trustworthy when GZIP is involved + // or other cases where an intermediate transformer has been applied + // to the stream. final Completer<Uint8List> completer = new Completer<Uint8List>.sync(); - if (response.contentLength == -1) { - final List<List<int>> chunks = <List<int>>[]; - int contentLength = 0; - response.listen((List<int> chunk) { - chunks.add(chunk); - contentLength += chunk.length; - }, onDone: () { - final Uint8List bytes = new Uint8List(contentLength); - int offset = 0; - for (List<int> chunk in chunks) { - bytes.setRange(offset, offset + chunk.length, chunk); - offset += chunk.length; - } - completer.complete(bytes); - }, onError: completer.completeError, cancelOnError: true); - } else { - // If the response has a content length, then allocate a buffer of the correct size. - final Uint8List bytes = new Uint8List(response.contentLength); + final List<List<int>> chunks = <List<int>>[]; + int contentLength = 0; + response.listen((List<int> chunk) { + chunks.add(chunk); + contentLength += chunk.length; + }, onDone: () { + final Uint8List bytes = new Uint8List(contentLength); int offset = 0; - response.listen((List<int> chunk) { + for (List<int> chunk in chunks) { bytes.setRange(offset, offset + chunk.length, chunk); offset += chunk.length; - }, - onError: completer.completeError, - onDone: () { - completer.complete(bytes); - }, - cancelOnError: true); - } + } + completer.complete(bytes); + }, onError: completer.completeError, cancelOnError: true); + return completer.future; }
diff --git a/packages/flutter/test/foundation/consolidate_response_test.dart b/packages/flutter/test/foundation/consolidate_response_test.dart index 184d9a2..c967197 100644 --- a/packages/flutter/test/foundation/consolidate_response_test.dart +++ b/packages/flutter/test/foundation/consolidate_response_test.dart
@@ -24,21 +24,40 @@ final void Function() onDone = invocation.namedArguments[#onDone]; final bool cancelOnError = invocation.namedArguments[#cancelOnError]; - return new Stream<List<int>>.fromIterable(<List<int>>[chunkOne, chunkTwo]) - .listen(onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError); + return new Stream<List<int>>.fromIterable( + <List<int>>[chunkOne, chunkTwo]).listen( + onData, + onDone: onDone, + onError: onError, + cancelOnError: cancelOnError, + ); }); }); - test('Converts an HttpClientResponse with contentLength to bytes', () async { - when(response.contentLength).thenReturn(chunkOne.length + chunkTwo.length); - final List<int> bytes = await consolidateHttpClientResponseBytes(response); + test('Converts an HttpClientResponse with contentLength to bytes', + () async { + when(response.contentLength) + .thenReturn(chunkOne.length + chunkTwo.length); + final List<int> bytes = + await consolidateHttpClientResponseBytes(response); expect(bytes, <int>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); - test('Converts an HttpClientResponse without contentLength to bytes', () async { + test('Converts a compressed HttpClientResponse with contentLength to bytes', + () async { + when(response.contentLength).thenReturn(chunkOne.length); + final List<int> bytes = + await consolidateHttpClientResponseBytes(response); + + expect(bytes, <int>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + }); + + test('Converts an HttpClientResponse without contentLength to bytes', + () async { when(response.contentLength).thenReturn(-1); - final List<int> bytes = await consolidateHttpClientResponseBytes(response); + final List<int> bytes = + await consolidateHttpClientResponseBytes(response); expect(bytes, <int>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); @@ -55,12 +74,19 @@ final void Function() onDone = invocation.namedArguments[#onDone]; final bool cancelOnError = invocation.namedArguments[#cancelOnError]; - return new Stream<List<int>>.fromFuture(new Future<List<int>>.error(new Exception('Test Error'))) - .listen(onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError); + return new Stream<List<int>>.fromFuture( + new Future<List<int>>.error(new Exception('Test Error'))) + .listen( + onData, + onDone: onDone, + onError: onError, + cancelOnError: cancelOnError, + ); }); when(response.contentLength).thenReturn(-1); - expect(consolidateHttpClientResponseBytes(response), throwsA(const isInstanceOf<Exception>())); + expect(consolidateHttpClientResponseBytes(response), + throwsA(const isInstanceOf<Exception>())); }); }); }