Skip to content

Commit 911820c

Browse files
committed
refactor: Fold exception handling into RequestPreparationFilter
Move inner-chain exception catching from ExceptionFilter into RequestPreparationFilter so after-hooks can fire inside the still-bound ScopedValue block (Task 4 of 9 — behavior-preserving). ExceptionFilter is kept and still wires onto extra-route contexts.
1 parent 93b91b9 commit 911820c

3 files changed

Lines changed: 84 additions & 23 deletions

File tree

src/main/java/com/retailsvc/http/OpenApiServer.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,15 @@ record HandlerConfig(
9595
httpServer.setExecutor(newThreadPerTaskExecutor(ofVirtual().name("http-", 0).factory()));
9696

9797
HttpContext ctx = httpServer.createContext(Optional.ofNullable(spec.basePath()).orElse("/"));
98-
ctx.getFilters().add(new ExceptionFilter(exceptionHandler));
99-
ctx.getFilters().add(new RequestPreparationFilter(spec, router, validator, bodyMappers));
98+
ctx.getFilters()
99+
.add(
100+
new RequestPreparationFilter(
101+
spec,
102+
router,
103+
validator,
104+
bodyMappers,
105+
exceptionHandler,
106+
handlerConfig.afterHooks()));
100107
ctx.getFilters()
101108
.add(
102109
new SecurityFilter(

src/main/java/com/retailsvc/http/internal/RequestPreparationFilter.java

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.retailsvc.http.internal;
22

3+
import com.retailsvc.http.AfterResponseHook;
4+
import com.retailsvc.http.ExceptionHandler;
35
import com.retailsvc.http.MethodNotAllowedException;
46
import com.retailsvc.http.NotFoundException;
57
import com.retailsvc.http.Request;
@@ -17,25 +19,38 @@
1719
import com.sun.net.httpserver.HttpExchange;
1820
import java.io.IOException;
1921
import java.util.HashMap;
22+
import java.util.List;
2023
import java.util.Locale;
2124
import java.util.Map;
2225
import java.util.Optional;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
2328

2429
public final class RequestPreparationFilter extends Filter {
2530

31+
private static final Logger LOG = LoggerFactory.getLogger(RequestPreparationFilter.class);
2632
private static final String BODY_POINTER = "/body";
2733

2834
private final Spec spec;
2935
private final Router router;
3036
private final Validator validator;
3137
private final Map<String, TypeMapper> bodyMappers;
38+
private final ExceptionHandler exceptionHandler;
39+
private final List<AfterResponseHook> afterHooks;
3240

3341
public RequestPreparationFilter(
34-
Spec spec, Router router, Validator validator, Map<String, TypeMapper> bodyMappers) {
42+
Spec spec,
43+
Router router,
44+
Validator validator,
45+
Map<String, TypeMapper> bodyMappers,
46+
ExceptionHandler exceptionHandler,
47+
List<AfterResponseHook> afterHooks) {
3548
this.spec = spec;
3649
this.router = router;
3750
this.validator = validator;
3851
this.bodyMappers = Map.copyOf(bodyMappers);
52+
this.exceptionHandler = exceptionHandler;
53+
this.afterHooks = List.copyOf(afterHooks);
3954
}
4055

4156
@Override
@@ -45,6 +60,30 @@ public String description() {
4560

4661
@Override
4762
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
63+
Request request;
64+
try {
65+
request = buildRequest(exchange);
66+
} catch (RuntimeException | IOException t) {
67+
exceptionHandler.handle(exchange, t);
68+
return;
69+
}
70+
71+
try {
72+
ScopedValue.where(DispatchHandler.CURRENT, request)
73+
.call(
74+
() -> {
75+
runInnerChain(exchange, chain);
76+
fireAfterHooks(exchange, request);
77+
return null;
78+
});
79+
} catch (IOException | RuntimeException e) {
80+
throw e;
81+
} catch (Exception e) {
82+
throw new IOException(e);
83+
}
84+
}
85+
86+
private Request buildRequest(HttpExchange exchange) throws IOException {
4887
byte[] body = exchange.getRequestBody().readAllBytes();
4988

5089
HttpMethod method = HttpMethod.parse(exchange.getRequestMethod());
@@ -65,30 +104,28 @@ public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
65104
ParsedBody parsedBody = validateAndParseBody(exchange, op, body);
66105

67106
var headers = exchange.getRequestHeaders();
68-
Request request =
69-
new Request(
70-
body,
71-
parsedBody.value(),
72-
parsedBody.mapper(),
73-
op.operationId(),
74-
match.pathParameters(),
75-
exchange.getRequestURI().getRawQuery(),
76-
headers::getFirst);
107+
return new Request(
108+
body,
109+
parsedBody.value(),
110+
parsedBody.mapper(),
111+
op.operationId(),
112+
match.pathParameters(),
113+
exchange.getRequestURI().getRawQuery(),
114+
headers::getFirst);
115+
}
77116

117+
private void runInnerChain(HttpExchange exchange, Chain chain) throws IOException {
78118
try {
79-
ScopedValue.where(DispatchHandler.CURRENT, request)
80-
.call(
81-
() -> {
82-
chain.doFilter(exchange);
83-
return null;
84-
});
85-
} catch (IOException | RuntimeException e) {
86-
throw e;
87-
} catch (Exception e) {
88-
throw new IOException(e);
119+
chain.doFilter(exchange);
120+
} catch (RuntimeException | IOException t) {
121+
exceptionHandler.handle(exchange, t);
89122
}
90123
}
91124

125+
private void fireAfterHooks(HttpExchange exchange, Request request) {
126+
// implemented in Task 5
127+
}
128+
92129
private String stripBasePath(String path) {
93130
String base = spec.basePath();
94131
if (base == null || base.isEmpty() || base.equals("/")) {

src/test/java/com/retailsvc/http/internal/RequestPreparationFilterTest.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.assertj.core.api.Assertions.assertThatThrownBy;
55
import static org.mockito.Mockito.mock;
66

7+
import com.retailsvc.http.ExceptionHandler;
78
import com.retailsvc.http.MethodNotAllowedException;
89
import com.retailsvc.http.NotFoundException;
910
import com.retailsvc.http.Request;
@@ -26,6 +27,7 @@
2627
import com.sun.net.httpserver.Headers;
2728
import com.sun.net.httpserver.HttpExchange;
2829
import java.io.ByteArrayInputStream;
30+
import java.io.IOException;
2931
import java.net.URI;
3032
import java.nio.charset.StandardCharsets;
3133
import java.util.List;
@@ -77,8 +79,23 @@ public byte[] writeTo(Object value) {
7779
}
7880
};
7981
Map<String, TypeMapper> mappers = Map.of("application/json", textMapper);
82+
ExceptionHandler rethrow =
83+
(exchange, t) -> {
84+
if (t instanceof IOException ioe) {
85+
throw ioe;
86+
}
87+
if (t instanceof RuntimeException re) {
88+
throw re;
89+
}
90+
throw new IOException(t);
91+
};
8092
return new RequestPreparationFilter(
81-
spec, new Router(spec.operations()), new DefaultValidator(spec::resolveSchema), mappers);
93+
spec,
94+
new Router(spec.operations()),
95+
new DefaultValidator(spec::resolveSchema),
96+
mappers,
97+
rethrow,
98+
List.of());
8299
}
83100

84101
@Test

0 commit comments

Comments
 (0)