Skip to content

Commit 54d483c

Browse files
committed
feat: Fire after-response hooks inside the request scope
DispatchHandler stashes the rendered Response as an exchange attribute so fireAfterHooks can pass the real response to global and per-request hooks. resolveResponse falls back to synthesising a Response from exchange headers/status on the error path. A try/finally around runInnerChain ensures hooks fire even when exceptionHandler.handle itself throws.
1 parent 911820c commit 54d483c

3 files changed

Lines changed: 48 additions & 5 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,8 @@ public void afterResponse(Runnable runnable) {
269269
afterHooks.add(runnable);
270270
}
271271

272-
/** Package-private accessor for the after-hook queue; used by RequestPreparationFilter. */
273-
List<Runnable> afterHooks() {
272+
/** Internal accessor for the after-hook queue; used by RequestPreparationFilter. */
273+
public List<Runnable> afterHooks() {
274274
return afterHooks;
275275
}
276276

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
public final class DispatchHandler implements HttpHandler {
1616

1717
public static final ScopedValue<Request> CURRENT = ScopedValue.newInstance();
18+
public static final String RESPONSE_ATTR = "com.retailsvc.http.response";
1819

1920
private final Map<String, RequestHandler> handlers;
2021
private final List<RequestInterceptor> interceptors;
@@ -43,6 +44,7 @@ public void handle(HttpExchange exchange) throws IOException {
4344
for (ResponseDecorator decorator : decorators) {
4445
response = decorator.decorate(request, response);
4546
}
47+
exchange.setAttribute(RESPONSE_ATTR, response);
4648
renderer.render(exchange, response);
4749
}
4850

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

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.retailsvc.http.MethodNotAllowedException;
66
import com.retailsvc.http.NotFoundException;
77
import com.retailsvc.http.Request;
8+
import com.retailsvc.http.Response;
89
import com.retailsvc.http.TypeMapper;
910
import com.retailsvc.http.ValidationException;
1011
import com.retailsvc.http.spec.HttpMethod;
@@ -16,9 +17,11 @@
1617
import com.retailsvc.http.validate.ValidationError;
1718
import com.retailsvc.http.validate.Validator;
1819
import com.sun.net.httpserver.Filter;
20+
import com.sun.net.httpserver.Headers;
1921
import com.sun.net.httpserver.HttpExchange;
2022
import java.io.IOException;
2123
import java.util.HashMap;
24+
import java.util.LinkedHashMap;
2225
import java.util.List;
2326
import java.util.Locale;
2427
import java.util.Map;
@@ -72,8 +75,11 @@ public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
7275
ScopedValue.where(DispatchHandler.CURRENT, request)
7376
.call(
7477
() -> {
75-
runInnerChain(exchange, chain);
76-
fireAfterHooks(exchange, request);
78+
try {
79+
runInnerChain(exchange, chain);
80+
} finally {
81+
fireAfterHooks(exchange, request);
82+
}
7783
return null;
7884
});
7985
} catch (IOException | RuntimeException e) {
@@ -123,7 +129,42 @@ private void runInnerChain(HttpExchange exchange, Chain chain) throws IOExceptio
123129
}
124130

125131
private void fireAfterHooks(HttpExchange exchange, Request request) {
126-
// implemented in Task 5
132+
Response response = resolveResponse(exchange);
133+
List<Runnable> snapshot = List.copyOf(request.afterHooks());
134+
135+
for (AfterResponseHook hook : afterHooks) {
136+
try {
137+
hook.after(request, response);
138+
} catch (Throwable t) {
139+
LOG.debug("after-response hook threw", t);
140+
}
141+
}
142+
for (Runnable runnable : snapshot) {
143+
try {
144+
runnable.run();
145+
} catch (Throwable t) {
146+
LOG.debug("after-response runnable threw", t);
147+
}
148+
}
149+
}
150+
151+
private static Response resolveResponse(HttpExchange exchange) {
152+
Object stashed = exchange.getAttribute(DispatchHandler.RESPONSE_ATTR);
153+
if (stashed instanceof Response r) {
154+
return r;
155+
}
156+
Headers headers = exchange.getResponseHeaders();
157+
String contentType = headers != null ? headers.getFirst("Content-Type") : null;
158+
Map<String, String> flat = new LinkedHashMap<>();
159+
if (headers != null) {
160+
for (Map.Entry<String, List<String>> e : headers.entrySet()) {
161+
List<String> values = e.getValue();
162+
if (values != null && !values.isEmpty()) {
163+
flat.put(e.getKey(), values.get(0));
164+
}
165+
}
166+
}
167+
return new Response(exchange.getResponseCode(), null, contentType, flat);
127168
}
128169

129170
private String stripBasePath(String path) {

0 commit comments

Comments
 (0)