Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletions packages/flutter/lib/src/foundation/consolidate_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ typedef BytesReceivedCallback = void Function(int cumulative, int total);
/// bytes from this method (assuming the response is sending compressed bytes),
/// set both [HttpClient.autoUncompress] to false and the `autoUncompress`
/// parameter to false.
// TODO(tvolkert): Remove the [client] param once https://github.com/dart-lang/sdk/issues/36971 is fixed.
Future<Uint8List> consolidateHttpClientResponseBytes(
HttpClientResponse response, {
HttpClient client,
bool autoUncompress = true,
BytesReceivedCallback onBytesReceived,
}) {
Expand All @@ -56,21 +58,15 @@ Future<Uint8List> consolidateHttpClientResponseBytes(
int expectedContentLength = response.contentLength;
if (expectedContentLength == -1)
expectedContentLength = null;
switch (response.compressionState) {
case HttpClientResponseCompressionState.compressed:
if (autoUncompress) {
// We need to un-compress the bytes as they come in.
sink = gzip.decoder.startChunkedConversion(output);
}
break;
case HttpClientResponseCompressionState.decompressed:
if (response.headers?.value(HttpHeaders.contentEncodingHeader) == 'gzip') {
if (client?.autoUncompress ?? true) {
// response.contentLength will not match our bytes stream, so we declare
// that we don't know the expected content length.
expectedContentLength = null;
break;
case HttpClientResponseCompressionState.notCompressed:
// Fall-through.
break;
} else if (autoUncompress) {
// We need to un-compress the bytes as they come in.
sink = gzip.decoder.startChunkedConversion(output);
}
}

int bytesReceived = 0;
Expand Down
1 change: 1 addition & 0 deletions packages/flutter/lib/src/painting/image_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ class NetworkImage extends ImageProvider<NetworkImage> {

final Uint8List bytes = await consolidateHttpClientResponseBytes(
response,
client: _httpClient,
onBytesReceived: (int cumulative, int total) {
chunkEvents.add(ImageChunkEvent(
cumulativeBytesLoaded: cumulative,
Expand Down
41 changes: 26 additions & 15 deletions packages/flutter/test/foundation/consolidate_response_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ void main() {
group(consolidateHttpClientResponseBytes, () {
final List<int> chunkOne = <int>[0, 1, 2, 3, 4, 5];
final List<int> chunkTwo = <int>[6, 7, 8, 9, 10];
MockHttpClient client;
MockHttpClientResponse response;
MockHttpHeaders headers;

setUp(() {
client = MockHttpClient();
response = MockHttpClientResponse();
when(response.compressionState).thenReturn(HttpClientResponseCompressionState.notCompressed);
headers = MockHttpHeaders();
when(client.autoUncompress).thenReturn(true);
when(response.headers).thenReturn(headers);
when(headers.value(HttpHeaders.contentEncodingHeader)).thenReturn(null);
when(response.listen(
any,
onDone: anyNamed('onDone'),
Expand All @@ -44,23 +50,23 @@ void main() {
when(response.contentLength)
.thenReturn(chunkOne.length + chunkTwo.length);
final List<int> bytes =
await consolidateHttpClientResponseBytes(response);
await consolidateHttpClientResponseBytes(response, client: client);

expect(bytes, <int>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
});

test('Converts a compressed HttpClientResponse with contentLength to bytes', () async {
when(response.contentLength).thenReturn(chunkOne.length);
final List<int> bytes =
await consolidateHttpClientResponseBytes(response);
await consolidateHttpClientResponseBytes(response, client: client);

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);
await consolidateHttpClientResponseBytes(response, client: client);

expect(bytes, <int>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
});
Expand All @@ -71,6 +77,7 @@ void main() {
final List<int> records = <int>[];
await consolidateHttpClientResponseBytes(
response,
client: client,
onBytesReceived: (int cumulative, int total) {
records.addAll(<int>[cumulative, total]);
},
Expand Down Expand Up @@ -107,14 +114,15 @@ void main() {
});
when(response.contentLength).thenReturn(-1);

expect(consolidateHttpClientResponseBytes(response),
expect(consolidateHttpClientResponseBytes(response, client: client),
throwsA(isInstanceOf<Exception>()));
});

test('Propagates error to Future return value if onBytesReceived throws', () async {
when(response.contentLength).thenReturn(-1);
final Future<List<int>> result = consolidateHttpClientResponseBytes(
response,
client: client,
onBytesReceived: (int cumulative, int total) {
throw 'misbehaving callback';
},
Expand All @@ -129,7 +137,7 @@ void main() {
final List<int> gzippedChunkTwo = gzipped.sublist(gzipped.length ~/ 2);

setUp(() {
when(response.compressionState).thenReturn(HttpClientResponseCompressionState.compressed);
when(headers.value(HttpHeaders.contentEncodingHeader)).thenReturn('gzip');
when(response.listen(
any,
onDone: anyNamed('onDone'),
Expand All @@ -151,26 +159,27 @@ void main() {
});
});

test('Uncompresses GZIP bytes if autoUncompress is true and response.compressionState is compressed', () async {
when(response.compressionState).thenReturn(HttpClientResponseCompressionState.compressed);
test('Uncompresses GZIP bytes if autoUncompress is true and response.autoUncompress is false', () async {
when(client.autoUncompress).thenReturn(false);
when(response.contentLength).thenReturn(gzipped.length);
final List<int> bytes = await consolidateHttpClientResponseBytes(response);
final List<int> bytes = await consolidateHttpClientResponseBytes(response, client: client);
expect(bytes, <int>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
});

test('returns gzipped bytes if autoUncompress is false and response.compressionState is compressed', () async {
when(response.compressionState).thenReturn(HttpClientResponseCompressionState.compressed);
test('returns gzipped bytes if autoUncompress is false and response.autoUncompress is false', () async {
when(client.autoUncompress).thenReturn(false);
when(response.contentLength).thenReturn(gzipped.length);
final List<int> bytes = await consolidateHttpClientResponseBytes(response, autoUncompress: false);
final List<int> bytes = await consolidateHttpClientResponseBytes(response, client: client, autoUncompress: false);
expect(bytes, gzipped);
});

test('Notifies onBytesReceived with gzipped numbers', () async {
when(response.compressionState).thenReturn(HttpClientResponseCompressionState.compressed);
when(client.autoUncompress).thenReturn(false);
when(response.contentLength).thenReturn(gzipped.length);
final List<int> records = <int>[];
await consolidateHttpClientResponseBytes(
response,
client: client,
onBytesReceived: (int cumulative, int total) {
records.addAll(<int>[cumulative, total]);
},
Expand All @@ -184,13 +193,13 @@ void main() {
]);
});

test('Notifies onBytesReceived with expectedContentLength of -1 if response.compressionState is decompressed', () async {
test('Notifies onBytesReceived with expectedContentLength of -1 if response.autoUncompress is true', () async {
final int syntheticTotal = (chunkOne.length + chunkTwo.length) * 2;
when(response.compressionState).thenReturn(HttpClientResponseCompressionState.decompressed);
when(response.contentLength).thenReturn(syntheticTotal);
final List<int> records = <int>[];
await consolidateHttpClientResponseBytes(
response,
client: client,
onBytesReceived: (int cumulative, int total) {
records.addAll(<int>[cumulative, total]);
},
Expand All @@ -207,4 +216,6 @@ void main() {
});
}

class MockHttpClient extends Mock implements HttpClient {}
class MockHttpClientResponse extends Mock implements HttpClientResponse {}
class MockHttpHeaders extends Mock implements HttpHeaders {}