Skip to content

Commit e7d8130

Browse files
committed
PreMarching filter errors should be filtered as ordinary request filters
1 parent ac5020f commit e7d8130

File tree

2 files changed

+161
-12
lines changed

2 files changed

+161
-12
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2017-2023 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.http.server.tck.tests.filter;
17+
18+
import io.micronaut.context.annotation.Requires;
19+
import io.micronaut.http.HttpRequest;
20+
import io.micronaut.http.HttpResponse;
21+
import io.micronaut.http.HttpStatus;
22+
import io.micronaut.http.MediaType;
23+
import io.micronaut.http.MutableHttpRequest;
24+
import io.micronaut.http.MutableHttpResponse;
25+
import io.micronaut.http.annotation.Controller;
26+
import io.micronaut.http.annotation.Get;
27+
import io.micronaut.http.annotation.Produces;
28+
import io.micronaut.http.annotation.RequestFilter;
29+
import io.micronaut.http.annotation.ResponseFilter;
30+
import io.micronaut.http.annotation.ServerFilter;
31+
import io.micronaut.http.server.annotation.PreMatching;
32+
import io.micronaut.http.server.exceptions.ExceptionHandler;
33+
import io.micronaut.http.tck.AssertionUtils;
34+
import io.micronaut.http.tck.HttpResponseAssertion;
35+
import io.micronaut.http.tck.TestScenario;
36+
import jakarta.inject.Singleton;
37+
import org.junit.jupiter.api.Test;
38+
39+
import java.io.IOException;
40+
41+
import static io.micronaut.http.annotation.Filter.MATCH_ALL_PATTERN;
42+
43+
@SuppressWarnings({
44+
"java:S5960", // We're allowed assertions, as these are used in tests only
45+
"checkstyle:MissingJavadocType",
46+
"checkstyle:DesignForExtension"
47+
})
48+
public class ResponseFilterOnPreMatchExceptionHandlerTest {
49+
private static final String SPEC_NAME = "ResponseFilterOnPreMatchExceptionHandlerTest";
50+
51+
@Test
52+
public void exceptionHandlerTest() throws IOException {
53+
TestScenario.builder()
54+
.specName(SPEC_NAME)
55+
.request(HttpRequest.GET("/foo"))
56+
.assertion((server, request) -> AssertionUtils.assertDoesNotThrow(server, request, HttpResponseAssertion.builder()
57+
.status(HttpStatus.OK)
58+
.body("EXCEPTION MAPPER RESPONSE FILTER")
59+
.build()))
60+
.run();
61+
}
62+
63+
static class FooException extends RuntimeException {
64+
65+
}
66+
67+
@ServerFilter(MATCH_ALL_PATTERN)
68+
@Requires(property = "spec.name", value = SPEC_NAME)
69+
static class ErrorThrowingFilter {
70+
@PreMatching
71+
@RequestFilter
72+
public void onPreMatching(MutableHttpRequest<?> request) {
73+
throw new FooException();
74+
}
75+
76+
@ResponseFilter
77+
public void onResponse(MutableHttpResponse<?> response) {
78+
response.body(response.getBody(String.class).get() + " RESPONSE FILTER");
79+
}
80+
}
81+
82+
@Requires(property = "spec.name", value = SPEC_NAME)
83+
@Controller("/foo")
84+
static class FooController {
85+
@Produces(MediaType.TEXT_PLAIN)
86+
@Get
87+
String index() {
88+
return "Hello World";
89+
}
90+
}
91+
92+
@Requires(property = "spec.name", value = SPEC_NAME)
93+
@Singleton
94+
static class FooExceptionHandler implements ExceptionHandler<FooException, HttpResponse<?>> {
95+
96+
@Override
97+
public HttpResponse<?> handle(HttpRequest request, FooException exception) {
98+
return HttpResponse.ok("EXCEPTION MAPPER");
99+
}
100+
}
101+
}

http/src/main/java/io/micronaut/http/filter/FilterRunner.java

+60-12
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,8 @@ public final ExecutionFlow<HttpResponse<?>> run(HttpRequest<?> request,
234234
} else {
235235
// Pre-matching filters plus route match resolver
236236
var f = new RouteMatchResolverHttpFilter();
237-
filtersToRun.add(f);
237+
filtersToRun.add(0, f); // Response filter to resolve filters on error
238+
filtersToRun.add(f); // Request filter to resolve filters after preMatch filters
238239
iterator = filtersToRun.listIterator();
239240
f.filterIterator = iterator;
240241
}
@@ -396,33 +397,80 @@ private ExecutionFlow<FilterContext> processFailureFilterException(FilterContext
396397
final class RouteMatchResolverHttpFilter implements InternalHttpFilter {
397398

398399
private ListIterator<InternalHttpFilter> filterIterator;
400+
private boolean executed = false;
401+
402+
@Override
403+
public boolean isFiltersResponse() {
404+
return true;
405+
}
399406

400407
@Override
401408
public boolean isFiltersRequest() {
402409
return true;
403410
}
404411

412+
@Override
413+
public ExecutionFlow<FilterContext> processResponseFilter(FilterContext context, Throwable exceptionToFilter) {
414+
if (!executed) {
415+
cleanupAllNext();
416+
addFoundFilters(context.request());
417+
moveIteratorToEnd();
418+
return ExecutionFlow.just(context);
419+
}
420+
return ExecutionFlow.just(context);
421+
}
422+
405423
@Override
406424
public ExecutionFlow<FilterContext> processRequestFilter(FilterContext context) {
425+
if (filterIterator.nextIndex() == 1) {
426+
return ExecutionFlow.just(context);
427+
}
428+
executed = true;
407429
HttpRequest<?> request = context.request();
408430
try {
409431
doRouteMatch(request);
410432
return ExecutionFlow.just(context);
411433
} catch (Throwable throwable) {
412434
return processFailurePropagateException(throwable, context);
413435
} finally {
436+
cleanupAllPrevious();
437+
addFoundFilters(request);
438+
moveIteratorToBeginning();
439+
}
440+
}
441+
442+
private void moveIteratorToBeginning() {
443+
while (filterIterator.hasPrevious()) {
444+
filterIterator.previous();
445+
}
446+
}
447+
448+
private void moveIteratorToEnd() {
449+
while (filterIterator.hasNext()) {
450+
filterIterator.next();
451+
}
452+
}
453+
454+
private void addFoundFilters(HttpRequest<?> request) {
455+
List<InternalHttpFilter> postFilters = findInternalFiltersAfterRouteMatch(request);
456+
for (InternalHttpFilter postFilter : postFilters) {
457+
filterIterator.add(postFilter);
458+
}
459+
}
460+
461+
private void cleanupAllNext() {
462+
filterIterator.remove();
463+
while (filterIterator.hasNext()) {
464+
filterIterator.next();
465+
filterIterator.remove();
466+
}
467+
}
468+
469+
private void cleanupAllPrevious() {
470+
filterIterator.remove();
471+
while (filterIterator.hasPrevious()) {
472+
filterIterator.previous();
414473
filterIterator.remove();
415-
while (filterIterator.hasPrevious()) {
416-
filterIterator.previous();
417-
filterIterator.remove();
418-
}
419-
List<InternalHttpFilter> postFilters = findInternalFiltersAfterRouteMatch(request);
420-
for (InternalHttpFilter postFilter : postFilters) {
421-
filterIterator.add(postFilter);
422-
}
423-
while (filterIterator.hasPrevious()) {
424-
filterIterator.previous();
425-
}
426474
}
427475
}
428476
}

0 commit comments

Comments
 (0)