Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve fallback to null on empty responses #2285

Merged
merged 7 commits into from
Sep 3, 2024

Conversation

knaeckeKami
Copy link
Contributor

@knaeckeKami knaeckeKami commented Aug 18, 2024

fixes #2279

This improves handling of the following case:

  • ResponseType is json
  • Server Content-Type is json
  • Server Content-Length is nonzero
  • Response Body is empty

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

  • I have read the Documentation
  • I have searched for a similar pull request in the project and found none
  • I have updated this branch with the latest main branch to avoid conflicts (via merge from master or rebase)
  • I have added the required tests to prove the fix/feature I'm adding
  • I have updated the documentation (if necessary)
  • I have run the tests without failures
  • I have updated the CHANGELOG.md in the corresponding package

Additional context and info (if any)

@knaeckeKami knaeckeKami requested a review from a team as a code owner August 18, 2024 16:27
dio/CHANGELOG.md Outdated Show resolved Hide resolved
dio/lib/src/transformers/util/transform_empty_to_null.dart Outdated Show resolved Hide resolved
@Schwusch
Copy link

Schwusch commented Aug 28, 2024

This is also a problem downstream in package:retrofit, where/when content type is always JSON but response body is empty.
Is there someone that can approve this? @ueman ?

@ueman
Copy link
Contributor

ueman commented Aug 28, 2024

@kuhnroyal @AlexV525 Do we want to release this as 5.7.0 or as a 6.0.0? This could be considered a breaking change, but it doesn't really feel like a big change. What do you think?

@knaeckeKami
Copy link
Contributor Author

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 ;)

@ueman
Copy link
Contributor

ueman commented Aug 28, 2024

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 5.7.0, but I still would like to hear their opinion

Copy link
Member

@AlexV525 AlexV525 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

dio/CHANGELOG.md Outdated
Comment on lines 8 to 9
- Graceful handling of Responses with nonzero Content-Length, Content-Type json, but empty body
- Empty responses are now transformed to `null`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two entries? Also please write words in correct capitalization: response, content-type, etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first line are the affected cases, the second line describes the result

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +24 to +26
/// Hard-coded constant for replacement value, "null"
static final Uint8List _nullUtf8Value =
Uint8List.fromList(const [110, 117, 108, 108]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the value help with?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't that cause a side effect that user won't understand why their response becomes "null"?

Copy link
Contributor Author

@knaeckeKami knaeckeKami Aug 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ResponseType is .json and
  • the Content-Type header is application/json and
  • the Content-Length header 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.

@knaeckeKami
Copy link
Contributor Author

I added a test here:
https://github.com/cfug/dio/pull/2285/files#diff-60c4759d76b4a7475d5c699664cbe409ca3cf5c6f09a403a5f3d2a4c4dd08ed6R330

This test would pass before #2250, and after this PR is merged.
With the current release, it would fail.

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 null instead of throwing an exception).

Signed-off-by: Martin Kamleithner <[email protected]>
Copy link
Contributor

Code Coverage Report: Only Changed Files listed

Package Base Coverage New Coverage Difference
dio/lib/src/transformers/fused_transformer.dart 🟢 91.3% 🟢 93.33% 🟢 2.03%
dio/lib/src/transformers/util/transform_empty_to_null.dart 🔴 0% 🟢 87.5% 🟢 87.5%
Overall Coverage 🟢 84.39% 🟢 84.46% 🟢 0.07%

Minimum allowed coverage is 0%, this run produced 84.46%

@ueman ueman merged commit d7cad71 into cfug:main Sep 3, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Dio Throws Exception on DELETE Request with 204 Response
4 participants