Skip to content

Commit

Permalink
Set FusedTransformer as default (#2250)
Browse files Browse the repository at this point in the history
<!-- Write down your pull request descriptions. -->

### New Pull Request Checklist

- [x] I have read the
[Documentation](https://pub.dev/documentation/dio/latest/)
- [x] I have searched for a similar pull request in the
[project](https://github.com/cfug/dio/pulls) and found none
- [x] I have updated this branch with the latest `main` branch to avoid
conflicts (via merge from master or rebase)
- [x] I have added the required tests to prove the fix/feature I'm
adding
- [x] I have updated the documentation (if necessary)
- [x] I have run the tests without failures
- [x] I have updated the `CHANGELOG.md` in the corresponding package

### Additional context and info (if any)

Also fix three oversights in the transformer

- Fix parsing content as JSON when Content-Type is like
`application/json` but `ResponseType == .plain`

- HEAD requests have an empty body, even when Content-Length is >0. See
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD . I did
not take this into account. This is also fixed now.

- Respect custom decoders returning null

---------

Signed-off-by: Martin Kamleithner <martin.kamleithner@gmail.com>
Co-authored-by: Alex Li <github@alexv525.com>
  • Loading branch information
knaeckeKami and AlexV525 authored Jun 18, 2024
1 parent 83c8a3f commit 47abf18
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 190 deletions.
1 change: 1 addition & 0 deletions dio/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ See the [Migration Guide][] for the complete breaking changes list.**
- Fix the type conversion regression when using `MultipartFile.fromBytes`.
- Split the Web implementation to `package:dio_web_adapter`.
- Add FusedTransformer for improved performance when decoding JSON.
- Set FusedTransformer as the default transformer.
- Improves `InterceptorState.toString()`.
- If the `CancelToken` got canceled before making requests,
throws the exception directly rather than cut actual HTTP requests afterward.
Expand Down
9 changes: 8 additions & 1 deletion dio/lib/src/dio_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,15 @@ abstract class DioMixin implements Dio {

/// The default [Transformer] that transfers requests and responses
/// into corresponding content to send.
/// For response bodies greater than 50KB, a new Isolate will be spawned to
/// decode the response body to JSON.
/// Taken from https://github.com/flutter/flutter/blob/135454af32477f815a7525073027a3ff9eff1bfd/packages/flutter/lib/src/services/asset_bundle.dart#L87-L93
/// 50 KB of data should take 2-3 ms to parse on a Moto G4, and about 400 μs
/// on a Pixel 4.
@override
Transformer transformer = BackgroundTransformer();
Transformer transformer = FusedTransformer(
contentLengthIsolateThreshold: 50 * 1024,
);

bool _closed = false;

Expand Down
31 changes: 22 additions & 9 deletions dio/lib/src/transformers/fused_transformer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ import 'util/consolidate_bytes.dart';
/// but a custom threshold can be set to switch to an isolate for large responses by passing
/// [contentLengthIsolateThreshold].
class FusedTransformer extends Transformer {
FusedTransformer({this.contentLengthIsolateThreshold = -1});
FusedTransformer({
this.contentLengthIsolateThreshold = -1,
});

/// Always decode the response in the same isolate
factory FusedTransformer.sync() =>
FusedTransformer(contentLengthIsolateThreshold: -1);
factory FusedTransformer.sync() => FusedTransformer(
contentLengthIsolateThreshold: -1,
);

// whether to switch decoding to an isolate for large responses
// set to -1 to disable, 0 to always use isolate
Expand Down Expand Up @@ -54,15 +57,16 @@ class FusedTransformer extends Transformer {
}

final isJsonContent = Transformer.isJsonMimeType(
responseBody.headers[Headers.contentTypeHeader]?.first,
);
responseBody.headers[Headers.contentTypeHeader]?.first,
) &&
responseType == ResponseType.json;

final customResponseDecoder = options.responseDecoder;

// No custom decoder was specified for the response,
// and the response is json -> use the fast path decoder
if (isJsonContent && customResponseDecoder == null) {
return _fastUtf8JsonDecode(responseBody);
return _fastUtf8JsonDecode(options, responseBody);
}
final responseBytes = await consolidateBytes(responseBody.stream);

Expand All @@ -88,7 +92,7 @@ class FusedTransformer extends Transformer {
if (isJsonContent && decodedResponse != null) {
// slow path decoder, since there was a custom decoder specified
return jsonDecode(decodedResponse);
} else if (decodedResponse != null) {
} else if (customResponseDecoder != null) {
return decodedResponse;
} else {
// If the response is not JSON and no custom decoder was specified,
Expand All @@ -100,7 +104,10 @@ class FusedTransformer extends Transformer {
}
}

Future<Object?> _fastUtf8JsonDecode(ResponseBody responseBody) async {
Future<Object?> _fastUtf8JsonDecode(
RequestOptions options,
ResponseBody responseBody,
) async {
final contentLengthHeader =
responseBody.headers[Headers.contentLengthHeader];

Expand All @@ -111,6 +118,11 @@ class FusedTransformer extends Transformer {
// of the response or the length of the eagerly decoded response bytes
final int contentLength;

// Successful HEAD requests don't have a response body, even if the content-length header
// present.
final mightNotHaveResponseBodyDespiteContentLength =
options.method == 'HEAD';

// The eagerly decoded response bytes
// which is set if the content length is not specified and
// null otherwise (we'll feed the stream directly to the decoder in that case)
Expand All @@ -119,7 +131,8 @@ class FusedTransformer extends Transformer {
// If the content length is not specified, we need to consolidate the stream
// and count the bytes to determine if we should use an isolate
// otherwise we use the content length header
if (!hasContentLengthHeader) {
if (!hasContentLengthHeader ||
mightNotHaveResponseBodyDespiteContentLength) {
responseBytes = await consolidateBytes(responseBody.stream);
contentLength = responseBytes.length;
} else {
Expand Down
Loading

0 comments on commit 47abf18

Please sign in to comment.