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

Inject CORS headers even when server-side errors occur #5632

Merged
merged 24 commits into from May 13, 2024

Conversation

taisey
Copy link
Contributor

@taisey taisey commented Apr 24, 2024

Motivation:

There is an issue where CORS headers are not added when exceptions occur while using CorsService.
CorsService does not inject CORS headers into error responses

Modifications:

Created CorsServerErrorHandler to inject CORS headers upon exceptions. Created CorsHeaderUtil and refactored CorsService, CorsPolicy.

Result:

CORS headers will be added to responses even if exceptions occur.

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.

Looks like the core logic is well done! Left some coding style comments. 🙇

@trustin
Copy link
Member

trustin commented Apr 26, 2024

Could you also make sure to fix all line errors, reported by Checkstyle?

@ikhoon
Copy link
Contributor

ikhoon commented Apr 26, 2024

Question)
In #5493, CORS headers weren't injected with .mapHeaders() because the HttpResponse was created from an exception or was aborted. HttpResponse.mapHeaders() can be invoked if an HttpResponse was successfully subscribed by HttpServerHandler.

It looks good to use ServerErrorHandler to inject CORS headers. However, for that, the CORS code is refactored quite a lot. Instead, there is a straightforward way to inject CORS headers in CorsService even under error responses. That is by using ServiceRequestContext.addAdditionalResonseHeaders() and its variants.

ctx.mutateAdditionalResponseHeaders(headers -> {
   setCorsResponseHeaders(ctx, req, headers);
});

I was wondering what is the advantage when using CorsExeptionHandler over using ctx.mutateAdditionalResponseHeaders? Maybe I'm missing something.

@trustin
Copy link
Member

trustin commented Apr 30, 2024

I was wondering what is the advantage when using CorsExeptionHandler over using ctx.mutateAdditionalResponseHeaders? Maybe I'm missing something.

It can inject CORS headers even when a decorator throws an exception before CorsService.serve() is even invoked. Do you think it's acceptable not to inject CORS headers in such cases?

@ikhoon
Copy link
Contributor

ikhoon commented May 3, 2024

Do you think it's acceptable not to inject CORS headers in such cases?

Other frameworks seem to use Filter pattern to inject CORS headers.
https://github.com/micronaut-projects/micronaut-core/blob/02a8378b7dd39c79e2b32313f78232d09caa8a45/http-server/src/main/java/io/micronaut/http/server/cors/CorsFilter.java#L140-L146
That means a former filter does not call the next chain in an error situation, CORS may not be injected.

I thought that we could use the same approach. If the decorator in front intercepts the request and returns an exception, CORS headers may not be necessary.

That said, I think the current approach is also good.

@jrhee17
Copy link
Contributor

jrhee17 commented May 7, 2024

I originally also thought of an approach similar to @ikhoon 's idea mainly due to the ease of implementation.

Do you think it's acceptable not to inject CORS headers in such cases?

I thought of this case as analogous to the success case.
e.g. If a decorator before CorsService returns with a HttpResponse(statusCode) early, then CORS headers won't be set.

Having said this, I agree with the current approach users have to worry less about the order of the CorsService decorator.
I think I'm fine with either approach.

@trustin
Copy link
Member

trustin commented May 7, 2024

@taisey Gentle ping - I think the general consensus here is that the current approache is good and we can continue cleaning up the PR and get it reviewed by other folks. Could you continue working on this PR?

@taisey
Copy link
Contributor Author

taisey commented May 8, 2024

@trustin Thank you for reaching out to me. If possible, I would like to continue working on this pull request.

@trustin
Copy link
Member

trustin commented May 8, 2024

@taisey Yes, please! Shall we continue addressing the review comments? We also need to fill the PR description following our commit message template here.

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! Left a few minor suggestions. 😉

HttpStatus status, @Nullable String description,
@Nullable Throwable cause) {

final CorsService corsService = serviceConfig.service().as(CorsService.class);
Copy link
Contributor

Choose a reason for hiding this comment

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

We can't simply find a CorsService added by ServerBuilder.decorate() with .as() operation.

All decorators would be searched from Router instead of using .as(). A possible workaround is to add a helper method to InitialDispatcherService.

public static class InitialDispatcherService extends SimpleDecoratingHttpService {

  @Nullable
  public <T extends HttpService> T findService(ServiceRequestContext ctx, Class<? extends T> serviceClass) {
    final List<Routed<RouteDecoratingService>> serviceChain = router.findAll(ctx.routingContext());
    if (serviceChain.isEmpty()) {
        return unwrap().as(serviceClass);
    }
    
    for (Routed<RouteDecoratingService> routed : serviceChain) {
        if (routed.isPresent()) {
            final HttpService service = routed.value().decorator();
            final T targetService = service.as(serviceClass);
            if (targetService != null) {
                return targetService;
            }
        }
    }
    return null;
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Created #5670
If the PR is merged, we can the code with:

final CorsService corsService = ctx.findService(CorsService.class);

Copy link
Member

Choose a reason for hiding this comment

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

Let me add a follow-up commit once it's merged. 😄

@trustin trustin marked this pull request as ready for review May 11, 2024 06:27
@trustin trustin requested a review from jrhee17 as a code owner May 11, 2024 06:27
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.

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.

Changes look straightforward! Thanks @taisey 👍 🙇 👍

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.

Looks great! Left a couple of nits. 😉

@minwoox minwoox added this to the 1.29.0 milestone May 13, 2024
@minwoox minwoox merged commit 450d5d5 into line:main May 13, 2024
15 checks passed
@minwoox
Copy link
Member

minwoox commented May 13, 2024

@taisey 👏 👏 👏 👍 👍 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants