fix: improve fallback to null on empty responses#2285
Conversation
Co-authored-by: Jonas Uekötter <ueman@users.noreply.github.com> Signed-off-by: Martin Kamleithner <martin.kamleithner@gmail.com>
…com/knaeckeKami/dio into fix/fused_transformer_null_fallback
|
This is also a problem downstream in |
|
@kuhnroyal @AlexV525 Do we want to release this as |
|
I'd argue it's a bug fix, as the change occurred with #2250, and this restores the old behavior. But of course, a bug fix can also be viewed as a breaking change if people depend on it ;) |
Yeah, I'm tempted to release it as |
AlexV525
left a comment
There was a problem hiding this comment.
I didn't take a detailed look into what happened. In general we need to provide tests that can pass in previous specific versions and with the request changes.
| - Graceful handling of Responses with nonzero Content-Length, Content-Type json, but empty body | ||
| - Empty responses are now transformed to `null` |
There was a problem hiding this comment.
Two entries? Also please write words in correct capitalization: response, content-type, etc.
There was a problem hiding this comment.
The first line are the affected cases, the second line describes the result
There was a problem hiding this comment.
fixed capitalization of "Responses".
The capitalization of the headers are like in in the official spec like https://datatracker.ietf.org/doc/html/rfc2616#section-14.13
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length
| /// Hard-coded constant for replacement value, "null" | ||
| static final Uint8List _nullUtf8Value = | ||
| Uint8List.fromList(const [110, 117, 108, 108]); |
There was a problem hiding this comment.
What does the value help with?
There was a problem hiding this comment.
It's for adding the byte sequence for "null" to the response stream, in the case that the response stream is unexpectedly empty.
So we return null instead of throwing an exception.
There was a problem hiding this comment.
Wouldn't that cause a side effect that user won't understand why their response becomes "null"?
There was a problem hiding this comment.
For clarification: The response won't become the String "null", it would become the value null after the json decoder decodes that byte sequence.
It has always been the behavior of Dio to return null when the ResponseType is .json and the response is empty.
Only in the recent release with the FusedTransformer there is now a corner case that if
- the
ResponseTypeis.jsonand - the
Content-Typeheader isapplication/jsonand - the
Content-Lengthheader has a value of > 0 and - the response is still empty
That an exception is thrown (instead of the old behavior of returning null).
This is because we currently assume that if the Content-Length is >0, that the response body will not be empty. So the response stream is fed into the json decoder, but the decoder throws on empty streams.
This transformation is not applied for other ResponseType than .json.
I would also be okay with removing the behavior of returning null on empty responses when the ResponseType is .json but the body is empty completely, and always throw an exception in that case.
But that would be a breaking change for sure.
The behavior of the current release, where it mostly returns null on empty responses but throws an exception in certain corner cases (mostly responses that are invalid according to the HTTP spec, but still occur in the real world), is not ideal IMO.
|
I added a test here: This test would pass before #2250, and after this PR is merged. A user opened #2279 because of that. The server sends an invalid response (according to the HTTP spec), but I still think that it's better to handle such cases gracefully in an HTTP client (especially if the old behavior was graceful (return |
Signed-off-by: Martin Kamleithner <martin.kamleithner@gmail.com>
Code Coverage Report: Only Changed Files listed
Minimum allowed coverage is |
…onseDecoder (#2550) ### Problem `FusedTransformer` is the default `Transformer`. When a request sets a custom `responseDecoder` **and** the server returns an empty body with a JSON content-type (a normal `200`/`204`/`304`), the transformer's slow path runs `jsonDecode('')`, throwing `FormatException: Unexpected end of input`. `SyncTransformer` and `BackgroundTransformer` handle the identical input gracefully — they guard the empty string and return it (which dio normalizes to `null` for typed JSON responses). Only the default transformer crashes. This is the residual half of #2279: PR #2285 fixed the empty-body → null behavior for the **fast path** (no custom decoder) but never guarded the custom-decoder slow path. ### Fix Add an `isNotEmpty` check to the slow-path guard so an empty decoded body falls through to the existing `return decodedResponse;` branch, matching `SyncTransformer` / `BackgroundTransformer`: ```dart if (isJsonContent && decodedResponse != null && decodedResponse.isNotEmpty) { return jsonDecode(decodedResponse); } else if (customResponseDecoder != null) { return decodedResponse; } ``` Non-empty malformed bodies still throw, exactly as before and as the sibling transformers do (the fix is intentionally scoped to match their `isNotEmpty`-only guard). ### 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) — n/a, no public API change - [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) Reproduction (no network needed), calling the default transformer directly: ```dart String decoder(List<int> b, RequestOptions o, ResponseBody rb) => utf8.decode(b, allowMalformed: true); await FusedTransformer().transformResponse( RequestOptions(responseType: ResponseType.json, responseDecoder: decoder), ResponseBody.fromBytes([], 200, headers: { Headers.contentTypeHeader: [Headers.jsonContentType], }), ); // before: throws FormatException: Unexpected end of input // after: returns '' (consistent with SyncTransformer / BackgroundTransformer) ``` The added regression test (`dio/test/transformer_test.dart` → *"empty JSON body with a custom responseDecoder does not throw"*) fails without the fix and passes with it. Co-authored-by: Alex Li <github@alexv525.com>
fixes #2279
This improves handling of the following case:
This can happen in some cases according to HTTP spec (e.g. HEAD requests), but some servers return responses like this (see #2279) also in other cases, even if it violates the HTTP spec.
With #2250, in some of these cases dio would throw an exception, which caused a regression for some users; see #2279
I do believe that dio should handle these cases for gracefully,
This PR brings back the previous behavior of returning null.
New Pull Request Checklist
mainbranch to avoid conflicts (via merge from master or rebase)CHANGELOG.mdin the corresponding packageAdditional context and info (if any)