diff --git a/core/src/main/java/com/linecorp/armeria/server/ContentPreviewServerErrorHandler.java b/core/src/main/java/com/linecorp/armeria/server/ContentPreviewErrorHandler.java similarity index 73% rename from core/src/main/java/com/linecorp/armeria/server/ContentPreviewServerErrorHandler.java rename to core/src/main/java/com/linecorp/armeria/server/ContentPreviewErrorHandler.java index 60f2d0c496e..c4b14aff26b 100644 --- a/core/src/main/java/com/linecorp/armeria/server/ContentPreviewServerErrorHandler.java +++ b/core/src/main/java/com/linecorp/armeria/server/ContentPreviewErrorHandler.java @@ -22,14 +22,11 @@ import com.linecorp.armeria.common.annotation.Nullable; import com.linecorp.armeria.server.logging.ContentPreviewingService; -final class ContentPreviewServerErrorHandler implements DecoratingServerErrorHandlerFunction { +final class ContentPreviewErrorHandler implements DecoratingErrorHandlerFunction { @Nullable - @Override - public HttpResponse onServiceException(ServerErrorHandler delegate, - ServiceRequestContext ctx, Throwable cause) { - final HttpResponse res = delegate.onServiceException(ctx, cause); - + private static HttpResponse maybeSetUpResponseContentPreviewer(ServiceRequestContext ctx, + @Nullable HttpResponse res) { final ContentPreviewingService contentPreviewingService = ctx.findService(ContentPreviewingService.class); if (contentPreviewingService == null) { @@ -45,4 +42,20 @@ public HttpResponse onServiceException(ServerErrorHandler delegate, ctx.logBuilder().responseContentPreview(null); return res; } + + @Nullable + @Override + public HttpResponse onServiceException(ServerErrorHandler delegate, + ServiceRequestContext ctx, Throwable cause) { + final HttpResponse res = delegate.onServiceException(ctx, cause); + return maybeSetUpResponseContentPreviewer(ctx, res); + } + + @Nullable + @Override + public HttpResponse onServiceException(ServiceErrorHandler delegate, + ServiceRequestContext ctx, Throwable cause) { + final HttpResponse res = delegate.onServiceException(ctx, cause); + return maybeSetUpResponseContentPreviewer(ctx, res); + } } diff --git a/core/src/main/java/com/linecorp/armeria/server/CorsServerErrorHandler.java b/core/src/main/java/com/linecorp/armeria/server/CorsErrorHandler.java similarity index 82% rename from core/src/main/java/com/linecorp/armeria/server/CorsServerErrorHandler.java rename to core/src/main/java/com/linecorp/armeria/server/CorsErrorHandler.java index 4943f6c2d97..0d7483220e1 100644 --- a/core/src/main/java/com/linecorp/armeria/server/CorsServerErrorHandler.java +++ b/core/src/main/java/com/linecorp/armeria/server/CorsErrorHandler.java @@ -26,9 +26,19 @@ import com.linecorp.armeria.server.cors.CorsService; /** - * wraps ServerErrorHandler for adding CORS headers to error responses. + * A {@link DecoratingErrorHandlerFunction} for adding CORS headers to error responses. */ -final class CorsServerErrorHandler implements DecoratingServerErrorHandlerFunction { +final class CorsErrorHandler implements DecoratingErrorHandlerFunction { + + private static void maybeSetCorsHeaders(ServiceRequestContext ctx) { + final CorsService corsService = ctx.findService(CorsService.class); + if (shouldSetCorsHeaders(corsService, ctx)) { + assert corsService != null; + ctx.mutateAdditionalResponseHeaders(builder -> { + CorsHeaderUtil.setCorsResponseHeaders(ctx, ctx.request(), builder, corsService.config()); + }); + } + } /** * Sets CORS headers for @@ -60,13 +70,15 @@ private static boolean shouldSetCorsHeaders(@Nullable CorsService corsService, S @Override public HttpResponse onServiceException(ServerErrorHandler delegate, ServiceRequestContext ctx, Throwable cause) { - final CorsService corsService = ctx.findService(CorsService.class); - if (shouldSetCorsHeaders(corsService, ctx)) { - assert corsService != null; - ctx.mutateAdditionalResponseHeaders(builder -> { - CorsHeaderUtil.setCorsResponseHeaders(ctx, ctx.request(), builder, corsService.config()); - }); - } + maybeSetCorsHeaders(ctx); + return delegate.onServiceException(ctx, cause); + } + + @Nullable + @Override + public HttpResponse onServiceException(ServiceErrorHandler delegate, + ServiceRequestContext ctx, Throwable cause) { + maybeSetCorsHeaders(ctx); return delegate.onServiceException(ctx, cause); } } diff --git a/core/src/main/java/com/linecorp/armeria/server/DecoratingServerErrorHandlerFunction.java b/core/src/main/java/com/linecorp/armeria/server/DecoratingErrorHandlerFunction.java similarity index 64% rename from core/src/main/java/com/linecorp/armeria/server/DecoratingServerErrorHandlerFunction.java rename to core/src/main/java/com/linecorp/armeria/server/DecoratingErrorHandlerFunction.java index 3a6658b6c32..0563817fe87 100644 --- a/core/src/main/java/com/linecorp/armeria/server/DecoratingServerErrorHandlerFunction.java +++ b/core/src/main/java/com/linecorp/armeria/server/DecoratingErrorHandlerFunction.java @@ -20,10 +20,15 @@ import com.linecorp.armeria.common.annotation.Nullable; /** - * A function to decorate a {@link ServerErrorHandler} with additional behavior. It is typically used to inject - * additional logic before or after the specified {@link ServerErrorHandler} execution. + * A function to decorate a {@link ServerErrorHandler} or a {@link ServiceErrorHandler} with additional + * behavior. Implementations can wrap error handlers to add pre-processing, post-processing, or + * conditional logic around error handling. The decorator receives the delegate error handler and + * can decide whether and when to invoke it. */ -interface DecoratingServerErrorHandlerFunction { +interface DecoratingErrorHandlerFunction { @Nullable HttpResponse onServiceException(ServerErrorHandler delegate, ServiceRequestContext ctx, Throwable cause); + + @Nullable + HttpResponse onServiceException(ServiceErrorHandler delegate, ServiceRequestContext ctx, Throwable cause); } diff --git a/core/src/main/java/com/linecorp/armeria/server/DefaultServerConfig.java b/core/src/main/java/com/linecorp/armeria/server/DefaultServerConfig.java index ce9a253fac2..ed6c0647f0b 100644 --- a/core/src/main/java/com/linecorp/armeria/server/DefaultServerConfig.java +++ b/core/src/main/java/com/linecorp/armeria/server/DefaultServerConfig.java @@ -258,7 +258,7 @@ final class DefaultServerConfig implements ServerConfig { this.enableServerHeader = enableServerHeader; this.enableDateHeader = enableDateHeader; - this.errorHandler = requireNonNull(errorHandler, "errorHandler"); + this.errorHandler = ErrorHandlerDecorators.decorate(requireNonNull(errorHandler, "errorHandler")); this.sslContexts = sslContexts; this.http1HeaderNaming = requireNonNull(http1HeaderNaming, "http1HeaderNaming"); this.dependencyInjector = requireNonNull(dependencyInjector, "dependencyInjector"); diff --git a/core/src/main/java/com/linecorp/armeria/server/ServerErrorHandlerDecorators.java b/core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java similarity index 58% rename from core/src/main/java/com/linecorp/armeria/server/ServerErrorHandlerDecorators.java rename to core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java index 6513a606a7a..faa2867cfea 100644 --- a/core/src/main/java/com/linecorp/armeria/server/ServerErrorHandlerDecorators.java +++ b/core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java @@ -26,29 +26,37 @@ import com.linecorp.armeria.common.RequestHeaders; import com.linecorp.armeria.common.annotation.Nullable; -final class ServerErrorHandlerDecorators { +final class ErrorHandlerDecorators { - private static final List decoratorFunctions = - ImmutableList.of(new CorsServerErrorHandler(), - new ContentPreviewServerErrorHandler()); + private static final List decoratorFunctions = + ImmutableList.of(new CorsErrorHandler(), + new ContentPreviewErrorHandler()); - private ServerErrorHandlerDecorators() {} + private ErrorHandlerDecorators() {} static ServerErrorHandler decorate(ServerErrorHandler delegate) { ServerErrorHandler decorated = delegate; - for (DecoratingServerErrorHandlerFunction decoratorFunction : decoratorFunctions) { + for (DecoratingErrorHandlerFunction decoratorFunction : decoratorFunctions) { decorated = new DecoratingServerErrorHandler(decorated, decoratorFunction); } return decorated; } + static ServiceErrorHandler decorate(ServiceErrorHandler delegate) { + ServiceErrorHandler decorated = delegate; + for (DecoratingErrorHandlerFunction decoratorFunction : decoratorFunctions) { + decorated = new DecoratingServiceErrorHandler(decorated, decoratorFunction); + } + return decorated; + } + private static final class DecoratingServerErrorHandler implements ServerErrorHandler { final ServerErrorHandler delegate; - final DecoratingServerErrorHandlerFunction decoratorFunction; + final DecoratingErrorHandlerFunction decoratorFunction; DecoratingServerErrorHandler(ServerErrorHandler delegate, - DecoratingServerErrorHandlerFunction decoratorFunction) { + DecoratingErrorHandlerFunction decoratorFunction) { this.delegate = delegate; this.decoratorFunction = decoratorFunction; } @@ -79,4 +87,32 @@ public AggregatedHttpResponse renderStatus(@Nullable ServiceRequestContext ctx, return delegate.renderStatus(ctx, config, headers, status, description, cause); } } + + private static final class DecoratingServiceErrorHandler implements ServiceErrorHandler { + + final ServiceErrorHandler delegate; + final DecoratingErrorHandlerFunction decoratorFunction; + + DecoratingServiceErrorHandler(ServiceErrorHandler delegate, + DecoratingErrorHandlerFunction decoratorFunction) { + this.delegate = delegate; + this.decoratorFunction = decoratorFunction; + } + + @Nullable + @Override + public HttpResponse onServiceException(ServiceRequestContext ctx, Throwable cause) { + return decoratorFunction.onServiceException(delegate, ctx, cause); + } + + @Nullable + @Override + public AggregatedHttpResponse renderStatus(ServiceRequestContext ctx, + RequestHeaders headers, + HttpStatus status, + @Nullable String description, + @Nullable Throwable cause) { + return delegate.renderStatus(ctx, headers, status, description, cause); + } + } } diff --git a/core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java b/core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java index 4502a6c93f7..df17f5f7136 100644 --- a/core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java @@ -2384,9 +2384,9 @@ DefaultServerConfig buildServerConfig(List serverPorts) { shutdownSupports.add(ShutdownSupport.of(tlsProvider)); } - final ServerErrorHandler errorHandler = ServerErrorHandlerDecorators.decorate( + final ServerErrorHandler errorHandler = this.errorHandler == null ? ServerErrorHandler.ofDefault() - : this.errorHandler.orElse(ServerErrorHandler.ofDefault())); + : this.errorHandler.orElse(ServerErrorHandler.ofDefault()); final MeterIdPrefix meterIdPrefix = tlsConfig != null ? tlsConfig.meterIdPrefix() : null; final SslContextFactory sslContextFactory = new SslContextFactory(meterIdPrefix, meterRegistry); final VirtualHost defaultVirtualHost = diff --git a/core/src/main/java/com/linecorp/armeria/server/ServiceConfigBuilder.java b/core/src/main/java/com/linecorp/armeria/server/ServiceConfigBuilder.java index 07b8ce6b90f..d46530ea0ae 100644 --- a/core/src/main/java/com/linecorp/armeria/server/ServiceConfigBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/server/ServiceConfigBuilder.java @@ -353,9 +353,9 @@ ServiceConfig build(ServiceNaming defaultServiceNaming, ServiceErrorHandler defaultServiceErrorHandler, @Nullable UnloggedExceptionsReporter unloggedExceptionsReporter, String baseContextPath, Supplier contextHook) { - ServiceErrorHandler errorHandler = + ServiceErrorHandler errorHandler = ErrorHandlerDecorators.decorate( serviceErrorHandler != null ? serviceErrorHandler.orElse(defaultServiceErrorHandler) - : defaultServiceErrorHandler; + : defaultServiceErrorHandler); if (unloggedExceptionsReporter != null) { errorHandler = new ExceptionReportingServiceErrorHandler(errorHandler, unloggedExceptionsReporter);