-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Update OptionsFilter to not emit duplicate headers #10126
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,20 +20,29 @@ | |
import io.micronaut.core.annotation.Nullable; | ||
import io.micronaut.core.order.Ordered; | ||
import io.micronaut.core.util.StringUtils; | ||
import io.micronaut.http.*; | ||
import io.micronaut.http.annotation.RequestFilter; | ||
import io.micronaut.http.HttpAttributes; | ||
import io.micronaut.http.HttpHeaders; | ||
import io.micronaut.http.HttpMethod; | ||
import io.micronaut.http.HttpRequest; | ||
import io.micronaut.http.HttpResponse; | ||
import io.micronaut.http.HttpStatus; | ||
import io.micronaut.http.MutableHttpResponse; | ||
import io.micronaut.http.annotation.ResponseFilter; | ||
import io.micronaut.http.annotation.ServerFilter; | ||
import io.micronaut.http.server.cors.CorsUtil; | ||
import io.micronaut.web.router.Router; | ||
import io.micronaut.web.router.UriRouteMatch; | ||
import io.micronaut.web.router.RouteMatch; | ||
import io.micronaut.web.router.UriRouteMatch; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
import static io.micronaut.http.annotation.Filter.MATCH_ALL_PATTERN; | ||
import static io.micronaut.http.server.cors.CorsFilter.CORS_FILTER_ORDER; | ||
|
||
/** | ||
* This Filter intercepts HTTP OPTIONS requests which are not CORS Preflight requests. | ||
* It responds with a NO_CONTENT(204) response, and it populates the Allow HTTP Header with the supported HTTP methods for the request URI. | ||
* It responds with an OK(200) response, and it populates the Allow HTTP Header with the supported HTTP methods for the request URI. | ||
* @author Sergio del Amo | ||
* @since 4.2.0 | ||
*/ | ||
|
@@ -45,20 +54,10 @@ public final class OptionsFilter implements Ordered { | |
@SuppressWarnings("WeakerAccess") | ||
public static final String PREFIX = HttpServerConfiguration.PREFIX + ".dispatch-options-requests"; | ||
|
||
private final Router router; | ||
|
||
/** | ||
* | ||
* @param router Router | ||
*/ | ||
public OptionsFilter(Router router) { | ||
this.router = router; | ||
} | ||
|
||
@RequestFilter | ||
@ResponseFilter | ||
@Nullable | ||
@Internal | ||
public HttpResponse<?> filterRequest(HttpRequest<?> request) { | ||
public HttpResponse<?> filterResponse(HttpRequest<?> request, MutableHttpResponse<?> response) { | ||
if (request.getMethod() != HttpMethod.OPTIONS) { | ||
return null; // proceed | ||
} | ||
|
@@ -68,13 +67,15 @@ public HttpResponse<?> filterRequest(HttpRequest<?> request) { | |
if (hasOptionsRouteMatch(request)) { | ||
return null; // proceed | ||
} | ||
MutableHttpResponse<?> mutableHttpResponse = HttpResponse.status(HttpStatus.OK); | ||
router.findAny(request.getUri().toString(), request) | ||
.map(UriRouteMatch::getHttpMethod) | ||
.map(HttpMethod::toString) | ||
.forEach(allow -> mutableHttpResponse.header(HttpHeaders.ALLOW, allow)); | ||
mutableHttpResponse.header(HttpHeaders.ALLOW, HttpMethod.OPTIONS.toString()); | ||
return mutableHttpResponse; | ||
if (HttpStatus.METHOD_NOT_ALLOWED.equals(response.getStatus())) { | ||
List<String> allowedMethods = response.getHeaders().get(HttpHeaders.ALLOW, String[].class) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't we have a getAll method to simplify this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, |
||
.map(allow -> new ArrayList<>(Arrays.asList(allow))).orElse(new ArrayList<>()); | ||
allowedMethods.add(HttpMethod.OPTIONS.toString()); | ||
response.getHeaders().remove(HttpHeaders.ALLOW); | ||
response.getHeaders().allowGeneric(allowedMethods); | ||
response.status(HttpStatus.OK); | ||
} | ||
return response; | ||
} | ||
|
||
private boolean hasOptionsRouteMatch(HttpRequest<?> request) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at this point might as well move the entire logic into RequestLifecycle or wherever METHOD_NOT_ALLOWED is thrown...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yawkat Yes, I had that same thought. It happens in RequestLifecycle#onRouteMiss.
My main concern, and reason for not doing that yet, is that
onRouteMiss
executes beforerunWithFilters
and the original intention of this filter to avoid conflicting with the CORS filter.That said, it occurs to me now that the
onRouteMiss
logic must be getting executed anyway (and theAllow
header getting populated) even in the case of CORS being enabled. Thus in a Servlet environment, we are likely sending theAllow
header even in response to a CORS pre-flight request (alongside all the other headers that CORS adds) because of the same issue of not being able to create a new ServletResponse in the middle of request processing.