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 not to use Http{Response,Status}Exception internally #4117

Merged
merged 4 commits into from
Mar 22, 2022

Conversation

ikhoon
Copy link
Contributor

@ikhoon ikhoon commented Feb 25, 2022

Motivation:

An HttpResponseException has an HttpResponse. The thrown response
is extracted by the default ServerErrorHandler and converted to
a normal HttpResponse. There are downsides to HttpResponseException.
As the response is wrapped by HttpResponseException,
decorators can not access the HttpObjects of the response in the
exception using mapXXX.

Users may find it hard to mutate the thrown HttpResponse because
recovery should be done first. Therefore, it would be better not to use
HttpResponseException internally or recover it before passing
the response to decorators so that users use HttpResponse.mapXXX to
transform a returned response in decorators.

Modifications:

  • Add HttpFile.ofRedirect(location) to indicate a file in a different
    location.
  • Recover an Http{Response,Status}Exception thrown by
    HealthCheckUpdateHandler
  • Update Javadoc in detail for Http{Response,Status}Exception
  • Correctly propagate the cause of Http{Response,Status}Exception in
    THttpService

Result:

Motivation:

An `HttpResponseException` has an `HttpResponse`. So the default
`ServerErrorHandler` extracts the `HttpResponse` from the exception.
As the response is wrapped by `HttpResponseException`,
decorators can not access the `HttpObject`s of the response in the
exception.

Users may find it hard to mutate the thrown `HttpResponse` because
recovery should be done first. So it would be better not to use
`HttpResponseException` or recover it before passing a response to
decorators.

Modifications:

- Add `HttpFile.ofRedirect(location)` to indicate a file in a different
  location.
- Recover an `Http{Response,Status}Exception thrown by
  `HealthCheckUpdateHandler`
- Update Javadoc in detail for `Http{Response,Status}Exception`
- Correctly propagate the cause of `Http{Response,Status}Exception` in
  `THttpService`

Result:

- You can now mutate a redirect response from `FileService` using
  `mapHeaders`
- You no longer see an `HttpResponseException` or
  an `HttpStatusException` from built-in services.
- Fixes line#4056
@ikhoon ikhoon added the defect label Feb 25, 2022
@ikhoon ikhoon added this to the 1.15.0 milestone Feb 25, 2022
@codecov
Copy link

codecov bot commented Feb 28, 2022

Codecov Report

Merging #4117 (353c02d) into master (829992a) will increase coverage by 0.05%.
The diff coverage is 84.61%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #4117      +/-   ##
============================================
+ Coverage     73.18%   73.24%   +0.05%     
- Complexity    16126    16137      +11     
============================================
  Files          1405     1405              
  Lines         61669    61694      +25     
  Branches       7765     7767       +2     
============================================
+ Hits          45134    45185      +51     
+ Misses        12612    12584      -28     
- Partials       3923     3925       +2     
Impacted Files Coverage Δ
...linecorp/armeria/server/HttpResponseException.java 78.57% <ø> (ø)
...m/linecorp/armeria/server/HttpStatusException.java 75.00% <ø> (ø)
.../com/linecorp/armeria/server/file/FileService.java 82.56% <ø> (-0.15%) ⬇️
...r/healthcheck/DefaultHealthCheckUpdateHandler.java 44.18% <0.00%> (-1.06%) ⬇️
...ava/com/linecorp/armeria/server/file/HttpFile.java 85.29% <100.00%> (+0.91%) ⬆️
...ecorp/armeria/server/file/NonExistentHttpFile.java 83.33% <100.00%> (+6.41%) ⬆️
...m/linecorp/armeria/server/thrift/THttpService.java 71.96% <100.00%> (-0.35%) ⬇️
...rnal/common/GracefulConnectionShutdownHandler.java 68.08% <0.00%> (-6.39%) ⬇️
...inecorp/armeria/server/file/StreamingHttpFile.java 54.78% <0.00%> (-2.61%) ⬇️
...ia/internal/common/stream/ByteBufDecoderInput.java 84.89% <0.00%> (-2.16%) ⬇️
... and 21 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 829992a...353c02d. Read the comment docs.

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Thanks, @ikhoon!

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

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

Looks great! Thank you @ikhoon 🙇 👍 🙇

Comment on lines 33 to 35
* <p>Note that an {@link HttpResponseException} raised may not be applied to the next decorators if the
* {@link HttpResponseException} is not recovered before passed to the next decorator chain.
* For that reason, you need to properly handle the thrown {@link HttpResponse} into a normal
Copy link
Contributor

Choose a reason for hiding this comment

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

since raised is describing the exception, HttpResponse -> HttpResponseException, better not to use pronouns "you", "I" in academic writing

Suggested change
* <p>Note that an {@link HttpResponseException} raised may not be applied to the next decorators if the
* {@link HttpResponseException} is not recovered before passed to the next decorator chain.
* For that reason, you need to properly handle the thrown {@link HttpResponse} into a normal
* <p>Note that a raised {@link HttpResponseException} may not be applied to the next decorators if the
* {@link HttpResponseException} is not recovered before being passed to the next decorator chain.
* For this reason, the thrown {@link HttpResponseException} should be converted into a normal

@@ -40,23 +44,41 @@
/**
* Tests if Armeria decorators can alter the request/response timeout specified in Thrift call parameters.
*/
public class ThriftHttpErrorResponseTest {
class ThriftHttpErrorResponseTest {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍👍👍

Comment on lines 31 to 33
* <p>Note that an {@link HttpStatusException} raised may not be applied to the next decorators if the
* {@link HttpStatusException} is not recovered before passed to the next decorator chain.
* For that reason, you need to properly handle the thrown {@link HttpStatus} into a normal
Copy link
Contributor

Choose a reason for hiding this comment

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

since raised is describing the exception, HttpStatus -> HttpStatusException, better not to use pronouns "you", "I" in academic writing

Suggested change
* <p>Note that an {@link HttpStatusException} raised may not be applied to the next decorators if the
* {@link HttpStatusException} is not recovered before passed to the next decorator chain.
* For that reason, you need to properly handle the thrown {@link HttpStatus} into a normal
* <p>Note that a raised {@link HttpStatusException} may not be applied to the next decorators if the
* {@link HttpStatusException} is not recovered before being passed to the next decorator chain.
* For this reason, the thrown {@link HttpStatusException} should be converted into a normal

Copy link
Contributor Author

Choose a reason for hiding this comment

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

TIL. ❤️

return HttpResponse.from(updateHandler.handle(ctx, req).handle((updateResult, cause) -> {
if (cause != null) {
cause = Exceptions.peel(cause);
return HttpResponse.ofFailure(cause);
Copy link
Contributor

Choose a reason for hiding this comment

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

Q) I understand that even with this change users won't be able to mutate response headers fluently from decorators - I want to make sure this was intended (no problem since I don't think the behavior is changed)

Copy link
Member

Choose a reason for hiding this comment

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

Ping @ikhoon

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 looks cruft. Let me revert this change. 😅

@@ -50,7 +51,7 @@
case PATCH:
return req.aggregate().thenApply(DefaultHealthCheckUpdateHandler::handlePatch);
default:
throw HttpStatusException.of(HttpStatus.METHOD_NOT_ALLOWED);
return exceptionallyCompletedFuture(HttpStatusException.of(HttpStatus.METHOD_NOT_ALLOWED));
Copy link
Member

Choose a reason for hiding this comment

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

nit: Could use UnmodifiableFuture.exceptionallyCompletedFuture()

return HttpResponse.from(updateHandler.handle(ctx, req).handle((updateResult, cause) -> {
if (cause != null) {
cause = Exceptions.peel(cause);
return HttpResponse.ofFailure(cause);
Copy link
Member

Choose a reason for hiding this comment

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

Ping @ikhoon

Copy link
Member

@trustin trustin left a comment

Choose a reason for hiding this comment

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

Thanks, @ikhoon !

@trustin trustin merged commit 43c63ea into line:master Mar 22, 2022
heowc pushed a commit to heowc/armeria that referenced this pull request Mar 25, 2022
Motivation:

An `HttpResponseException` has an `HttpResponse`. The thrown response
is extracted by the default `ServerErrorHandler` and converted to 
a normal `HttpResponse`. There are downsides to `HttpResponseException`.
As the response is wrapped by `HttpResponseException`,
decorators can not access the `HttpObject`s of the response in the
exception using `mapXXX`.

Users may find it hard to mutate the thrown `HttpResponse` because 
recovery should be done first. Therefore, it would be better not to use 
`HttpResponseException` internally or recover it before passing
the response to decorators so that users use `HttpResponse.mapXXX` to
transform a returned response in decorators.

Modifications:

- Add `HttpFile.ofRedirect(location)` to indicate a file in a different
  location.
- Recover an `Http{Response,Status}Exception` thrown by
  `HealthCheckUpdateHandler`
- Update Javadoc in detail for `Http{Response,Status}Exception`
- Correctly propagate the cause of `Http{Response,Status}Exception` in
  `THttpService`

Result:

- You can now mutate a redirect response from `FileService` using
  `mapHeaders`
- You no longer see an `HttpResponseException` or
  an `HttpStatusException` from built-in services.
- Fixes line#4056
@ikhoon ikhoon deleted the httpfile.redirect branch March 28, 2022 05:53
ikhoon added a commit to ikhoon/armeria that referenced this pull request Nov 25, 2022
Motivation:

We decided not to use `HttpStatusException` and `HttpResponseException`
internally because if an exception is thrown, it break through decorator
chains and we can't use stream operatorators in `HttpResponse`. line#4117
Thad said, `FallbackService` still throws defered `HttpStatusException`s
which we should've migrated in line#4117 but omitted it.

Modification:

- Return a normal `HttpResponse` instead of throwing
  `HttpStatusException` in `FallbackService`.

Result:

You no longer see `HttpStatusException` when there is no matching route.
trustin pushed a commit that referenced this pull request Apr 5, 2023
Motivation:

We decided not to use `HttpStatusException` and `HttpResponseException` internally because if an exception is thrown, it breaks through decorator chains and we can't use stream operators in `HttpResponse`. #4117 Thad said, `FallbackService` still throws deferred `HttpStatusException`s which we should've migrated in #4117 but omitted it.

Fixing the bug, I found out that there is a race condition between `FallbackService` and `Http*RequestDecoder`. If a request exceeds the maximum length limit, `Http*RequestDecoder` will return 413 Request Entity Too Large to the request. If a matching route is not found, `FallbackService` returns 404 Not Found. Armeria tests expect 404 for that case. 
https://github.com/line/armeria/blob/be306ab4c9de5386e74999baec5ebaae82618b91/core/src/test/java/com/linecorp/armeria/server/HttpServerTest.java#L598-L605

It was possible since `FallbackService` threw an exception that aborts a request immediately.
https://github.com/line/armeria/blob/8aebaf475eeee1ee3fec6c7c876456878a8b423e/core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java#L365-L372

If `FallbackService` returns a normal response, the request will be closed when a response is written completely. https://github.com/line/armeria/blob/8aebaf475eeee1ee3fec6c7c876456878a8b423e/core/src/main/java/com/linecorp/armeria/server/HttpServerHandler.java#L422-L429
As a result, `Http*RequestDecoder` can return a 413
response to the request being handled by `FallbackService`.

 
Modification:

- Return a normal `HttpResponse` instead of throwing `HttpStatusException` in `FallbackService`.
  - A headers-only response is used to finish the response quickly before `Http*RequestDecoder` receives additional messages.
- Fixed `Http1RequestDecoder.fail()` to check if a response to the request was sent before to avoid duplicate responses.

Result:

- You no longer see `HttpStatusException` when there is no matching route.
- Fixes #4548
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

HttpResponse.mapHeaders doesn't work with FileService redirect
4 participants