Skip to content

Commit 69ba77d

Browse files
committed
Add decorator support for ServiceErrorHandler
1 parent 6777284 commit 69ba77d

File tree

7 files changed

+133
-30
lines changed

7 files changed

+133
-30
lines changed

core/src/main/java/com/linecorp/armeria/server/ContentPreviewServerErrorHandler.java renamed to core/src/main/java/com/linecorp/armeria/server/ContentPreviewErrorHandler.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,11 @@
2222
import com.linecorp.armeria.common.annotation.Nullable;
2323
import com.linecorp.armeria.server.logging.ContentPreviewingService;
2424

25-
final class ContentPreviewServerErrorHandler implements DecoratingServerErrorHandlerFunction {
25+
final class ContentPreviewErrorHandler implements DecoratingErrorHandlerFunction {
2626

2727
@Nullable
28-
@Override
29-
public HttpResponse onServiceException(ServerErrorHandler delegate,
30-
ServiceRequestContext ctx, Throwable cause) {
31-
final HttpResponse res = delegate.onServiceException(ctx, cause);
32-
28+
private static HttpResponse maybeSetUpResponseContentPreviewer(ServiceRequestContext ctx,
29+
@Nullable HttpResponse res) {
3330
final ContentPreviewingService contentPreviewingService =
3431
ctx.findService(ContentPreviewingService.class);
3532
if (contentPreviewingService == null) {
@@ -45,4 +42,20 @@ public HttpResponse onServiceException(ServerErrorHandler delegate,
4542
ctx.logBuilder().responseContentPreview(null);
4643
return res;
4744
}
45+
46+
@Nullable
47+
@Override
48+
public HttpResponse onServiceException(ServerErrorHandler delegate,
49+
ServiceRequestContext ctx, Throwable cause) {
50+
final HttpResponse res = delegate.onServiceException(ctx, cause);
51+
return maybeSetUpResponseContentPreviewer(ctx, res);
52+
}
53+
54+
@Nullable
55+
@Override
56+
public HttpResponse onServiceException(ServiceErrorHandler delegate,
57+
ServiceRequestContext ctx, Throwable cause) {
58+
final HttpResponse res = delegate.onServiceException(ctx, cause);
59+
return maybeSetUpResponseContentPreviewer(ctx, res);
60+
}
4861
}

core/src/main/java/com/linecorp/armeria/server/CorsServerErrorHandler.java renamed to core/src/main/java/com/linecorp/armeria/server/CorsErrorHandler.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,19 @@
2626
import com.linecorp.armeria.server.cors.CorsService;
2727

2828
/**
29-
* wraps ServerErrorHandler for adding CORS headers to error responses.
29+
* A {@link DecoratingErrorHandlerFunction} for adding CORS headers to error responses.
3030
*/
31-
final class CorsServerErrorHandler implements DecoratingServerErrorHandlerFunction {
31+
final class CorsErrorHandler implements DecoratingErrorHandlerFunction {
32+
33+
private static void maybeSetCorsHeaders(ServiceRequestContext ctx) {
34+
final CorsService corsService = ctx.findService(CorsService.class);
35+
if (shouldSetCorsHeaders(corsService, ctx)) {
36+
assert corsService != null;
37+
ctx.mutateAdditionalResponseHeaders(builder -> {
38+
CorsHeaderUtil.setCorsResponseHeaders(ctx, ctx.request(), builder, corsService.config());
39+
});
40+
}
41+
}
3242

3343
/**
3444
* Sets CORS headers for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests">
@@ -60,13 +70,15 @@ private static boolean shouldSetCorsHeaders(@Nullable CorsService corsService, S
6070
@Override
6171
public HttpResponse onServiceException(ServerErrorHandler delegate,
6272
ServiceRequestContext ctx, Throwable cause) {
63-
final CorsService corsService = ctx.findService(CorsService.class);
64-
if (shouldSetCorsHeaders(corsService, ctx)) {
65-
assert corsService != null;
66-
ctx.mutateAdditionalResponseHeaders(builder -> {
67-
CorsHeaderUtil.setCorsResponseHeaders(ctx, ctx.request(), builder, corsService.config());
68-
});
69-
}
73+
maybeSetCorsHeaders(ctx);
74+
return delegate.onServiceException(ctx, cause);
75+
}
76+
77+
@Nullable
78+
@Override
79+
public HttpResponse onServiceException(ServiceErrorHandler delegate,
80+
ServiceRequestContext ctx, Throwable cause) {
81+
maybeSetCorsHeaders(ctx);
7082
return delegate.onServiceException(ctx, cause);
7183
}
7284
}

core/src/main/java/com/linecorp/armeria/server/DecoratingServerErrorHandlerFunction.java renamed to core/src/main/java/com/linecorp/armeria/server/DecoratingErrorHandlerFunction.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@
2020
import com.linecorp.armeria.common.annotation.Nullable;
2121

2222
/**
23-
* A function to decorate a {@link ServerErrorHandler} with additional behavior. It is typically used to inject
24-
* additional logic before or after the specified {@link ServerErrorHandler} execution.
23+
* A function to decorate a {@link ServerErrorHandler} or a {@link ServiceErrorHandler} with additional
24+
* behavior. Implementations can wrap error handlers to add pre-processing, post-processing, or
25+
* conditional logic around error handling. The decorator receives the delegate error handler and
26+
* can decide whether and when to invoke it.
2527
*/
26-
interface DecoratingServerErrorHandlerFunction {
28+
interface DecoratingErrorHandlerFunction {
2729
@Nullable
2830
HttpResponse onServiceException(ServerErrorHandler delegate, ServiceRequestContext ctx, Throwable cause);
31+
32+
@Nullable
33+
HttpResponse onServiceException(ServiceErrorHandler delegate, ServiceRequestContext ctx, Throwable cause);
2934
}

core/src/main/java/com/linecorp/armeria/server/ServerErrorHandlerDecorators.java renamed to core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,37 @@
2626
import com.linecorp.armeria.common.RequestHeaders;
2727
import com.linecorp.armeria.common.annotation.Nullable;
2828

29-
final class ServerErrorHandlerDecorators {
29+
final class ErrorHandlerDecorators {
3030

31-
private static final List<DecoratingServerErrorHandlerFunction> decoratorFunctions =
32-
ImmutableList.of(new CorsServerErrorHandler(),
33-
new ContentPreviewServerErrorHandler());
31+
private static final List<DecoratingErrorHandlerFunction> decoratorFunctions =
32+
ImmutableList.of(new CorsErrorHandler(),
33+
new ContentPreviewErrorHandler());
3434

35-
private ServerErrorHandlerDecorators() {}
35+
private ErrorHandlerDecorators() {}
3636

3737
static ServerErrorHandler decorate(ServerErrorHandler delegate) {
3838
ServerErrorHandler decorated = delegate;
39-
for (DecoratingServerErrorHandlerFunction decoratorFunction : decoratorFunctions) {
39+
for (DecoratingErrorHandlerFunction decoratorFunction : decoratorFunctions) {
4040
decorated = new DecoratingServerErrorHandler(decorated, decoratorFunction);
4141
}
4242
return decorated;
4343
}
4444

45+
static ServiceErrorHandler decorate(ServiceErrorHandler delegate) {
46+
ServiceErrorHandler decorated = delegate;
47+
for (DecoratingErrorHandlerFunction decoratorFunction : decoratorFunctions) {
48+
decorated = new DecoratingServiceErrorHandler(decorated, decoratorFunction);
49+
}
50+
return decorated;
51+
}
52+
4553
private static final class DecoratingServerErrorHandler implements ServerErrorHandler {
4654

4755
final ServerErrorHandler delegate;
48-
final DecoratingServerErrorHandlerFunction decoratorFunction;
56+
final DecoratingErrorHandlerFunction decoratorFunction;
4957

5058
DecoratingServerErrorHandler(ServerErrorHandler delegate,
51-
DecoratingServerErrorHandlerFunction decoratorFunction) {
59+
DecoratingErrorHandlerFunction decoratorFunction) {
5260
this.delegate = delegate;
5361
this.decoratorFunction = decoratorFunction;
5462
}
@@ -79,4 +87,32 @@ public AggregatedHttpResponse renderStatus(@Nullable ServiceRequestContext ctx,
7987
return delegate.renderStatus(ctx, config, headers, status, description, cause);
8088
}
8189
}
90+
91+
private static final class DecoratingServiceErrorHandler implements ServiceErrorHandler {
92+
93+
final ServiceErrorHandler delegate;
94+
final DecoratingErrorHandlerFunction decoratorFunction;
95+
96+
DecoratingServiceErrorHandler(ServiceErrorHandler delegate,
97+
DecoratingErrorHandlerFunction decoratorFunction) {
98+
this.delegate = delegate;
99+
this.decoratorFunction = decoratorFunction;
100+
}
101+
102+
@Nullable
103+
@Override
104+
public HttpResponse onServiceException(ServiceRequestContext ctx, Throwable cause) {
105+
return decoratorFunction.onServiceException(delegate, ctx, cause);
106+
}
107+
108+
@Nullable
109+
@Override
110+
public AggregatedHttpResponse renderStatus(ServiceRequestContext ctx,
111+
RequestHeaders headers,
112+
HttpStatus status,
113+
@Nullable String description,
114+
@Nullable Throwable cause) {
115+
return delegate.renderStatus(ctx, headers, status, description, cause);
116+
}
117+
}
82118
}

core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2335,7 +2335,7 @@ DefaultServerConfig buildServerConfig(List<ServerPort> serverPorts) {
23352335
shutdownSupports.add(ShutdownSupport.of(tlsProvider));
23362336
}
23372337

2338-
final ServerErrorHandler errorHandler = ServerErrorHandlerDecorators.decorate(
2338+
final ServerErrorHandler errorHandler = ErrorHandlerDecorators.decorate(
23392339
this.errorHandler == null ? ServerErrorHandler.ofDefault()
23402340
: this.errorHandler.orElse(ServerErrorHandler.ofDefault()));
23412341
final MeterIdPrefix meterIdPrefix = tlsConfig != null ? tlsConfig.meterIdPrefix() : null;

core/src/main/java/com/linecorp/armeria/server/ServiceConfigBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,9 @@ ServiceConfig build(ServiceNaming defaultServiceNaming,
353353
ServiceErrorHandler defaultServiceErrorHandler,
354354
@Nullable UnloggedExceptionsReporter unloggedExceptionsReporter,
355355
String baseContextPath, Supplier<AutoCloseable> contextHook) {
356-
ServiceErrorHandler errorHandler =
356+
ServiceErrorHandler errorHandler = ErrorHandlerDecorators.decorate(
357357
serviceErrorHandler != null ? serviceErrorHandler.orElse(defaultServiceErrorHandler)
358-
: defaultServiceErrorHandler;
358+
: defaultServiceErrorHandler);
359359
if (unloggedExceptionsReporter != null) {
360360
errorHandler = new ExceptionReportingServiceErrorHandler(errorHandler,
361361
unloggedExceptionsReporter);

core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.linecorp.armeria.common.logging.RequestLog;
3636
import com.linecorp.armeria.server.HttpStatusException;
3737
import com.linecorp.armeria.server.ServerBuilder;
38+
import com.linecorp.armeria.server.ServiceErrorHandler;
3839
import com.linecorp.armeria.server.annotation.ExceptionHandlerFunction;
3940
import com.linecorp.armeria.server.annotation.Get;
4041
import com.linecorp.armeria.server.annotation.ProducesText;
@@ -69,6 +70,34 @@ protected void configure(ServerBuilder sb) {
6970
return HttpResponse.of("exceptionHandler: " + cause.getMessage());
7071
});
7172

73+
final ServiceErrorHandler serviceErrorHandler = (ctx, cause) -> {
74+
return HttpResponse.of("serviceErrorHandler: " + cause.getMessage());
75+
};
76+
sb.route()
77+
.path("/binding/aborted/http-status-exception")
78+
.errorHandler(serviceErrorHandler)
79+
.build((ctx, req) -> {
80+
return HttpResponse.ofFailure(HttpStatusException.of(HttpStatus.INTERNAL_SERVER_ERROR));
81+
});
82+
sb.route()
83+
.path("/binding/aborted/unexpected-exception")
84+
.errorHandler(serviceErrorHandler)
85+
.build((ctx, req) -> {
86+
return HttpResponse.ofFailure(new IllegalStateException("Oops!"));
87+
});
88+
sb.route()
89+
.path("/binding/throw/http-status-exception")
90+
.errorHandler(serviceErrorHandler)
91+
.build((ctx, req) -> {
92+
throw HttpStatusException.of(HttpStatus.INTERNAL_SERVER_ERROR);
93+
});
94+
sb.route()
95+
.path("/binding/throw/unexpected-exception")
96+
.errorHandler(serviceErrorHandler)
97+
.build((ctx, req) -> {
98+
throw new IllegalStateException("Oops!");
99+
});
100+
72101
sb.decorator(LoggingService.newDecorator());
73102
sb.decorator(ContentPreviewingService.newDecorator(Integer.MAX_VALUE));
74103
}
@@ -137,7 +166,15 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
137166
Arguments.of("/annotatedExceptionHandler/throw/http-status-exception",
138167
"exceptionHandler: 500 Internal Server Error"),
139168
Arguments.of("/annotatedExceptionHandler/throw/unexpected-exception",
140-
"exceptionHandler: Oops!"));
169+
"exceptionHandler: Oops!"),
170+
Arguments.of("/binding/aborted/http-status-exception",
171+
"serviceErrorHandler: 500 Internal Server Error"),
172+
Arguments.of("/binding/aborted/unexpected-exception",
173+
"serviceErrorHandler: Oops!"),
174+
Arguments.of("/binding/throw/http-status-exception",
175+
"serviceErrorHandler: 500 Internal Server Error"),
176+
Arguments.of("/binding/throw/unexpected-exception",
177+
"serviceErrorHandler: Oops!"));
141178
}
142179
}
143180
}

0 commit comments

Comments
 (0)