From 69df40739f054f97910c355faf0b0baa5954d581 Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Mon, 25 Aug 2025 10:01:24 -0600 Subject: [PATCH 01/10] Allow an exception handler to be bound to handle unexpected for the HTTP worker --- build.savant | 2 +- pom.xml | 4 +-- .../fusionauth/http/server/Configurable.java | 13 ++++++++++ .../http/server/HTTPServerConfiguration.java | 19 ++++++++++++++ .../HTTPUnexpectedExceptionHandler.java | 25 +++++++++++++++++++ .../http/server/internal/HTTPWorker.java | 23 +++++++++++++++-- 6 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 src/main/java/io/fusionauth/http/server/HTTPUnexpectedExceptionHandler.java diff --git a/build.savant b/build.savant index dc347fc..dd70019 100644 --- a/build.savant +++ b/build.savant @@ -18,7 +18,7 @@ restifyVersion = "4.2.1" slf4jVersion = "2.0.17" testngVersion = "7.11.0" -project(group: "io.fusionauth", name: "java-http", version: "1.1.1", licenses: ["ApacheV2_0"]) { +project(group: "io.fusionauth", name: "java-http", version: "1.2.0", licenses: ["ApacheV2_0"]) { workflow { fetch { // Dependency resolution order: diff --git a/pom.xml b/pom.xml index c03ce31..426fdfa 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 io.fusionauth java-http - 1.1.1 + 1.2.0 jar Java HTTP library (client and server) @@ -200,4 +200,4 @@ - + \ No newline at end of file diff --git a/src/main/java/io/fusionauth/http/server/Configurable.java b/src/main/java/io/fusionauth/http/server/Configurable.java index ace87f6..43ca189 100644 --- a/src/main/java/io/fusionauth/http/server/Configurable.java +++ b/src/main/java/io/fusionauth/http/server/Configurable.java @@ -331,6 +331,19 @@ default T withShutdownDuration(Duration duration) { return (T) this; } + /** + * + * Sets the unexpected exception handler that may occur while processing an HTTP request. This can be set to null which means the HTTP + * worker will use the default behavior. + * + * @param unexpectedExceptionHandler The unexpected exception handler. + * @return This. + */ + default T withUnexpectedExceptionHandler(HTTPUnexpectedExceptionHandler unexpectedExceptionHandler) { + configuration().withUnexpectedExceptionHandler(unexpectedExceptionHandler); + return (T) this; + } + /** * This configures the duration of the initial delay before calculating and enforcing the minimum write throughput. Defaults to 5 * seconds. diff --git a/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java b/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java index 113a8b8..09b6bc1 100644 --- a/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java +++ b/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java @@ -79,6 +79,8 @@ public class HTTPServerConfiguration implements Configurable Date: Mon, 25 Aug 2025 19:11:38 -0600 Subject: [PATCH 02/10] updates per code review, simplification. --- .../fusionauth/http/server/Configurable.java | 8 +++-- ...DefaultHTTPUnexpectedExceptionHandler.java | 33 +++++++++++++++++++ .../http/server/HTTPServerConfiguration.java | 17 +++++----- .../HTTPUnexpectedExceptionHandler.java | 19 +++++++++-- .../http/server/internal/HTTPWorker.java | 21 ++---------- 5 files changed, 66 insertions(+), 32 deletions(-) create mode 100644 src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java diff --git a/src/main/java/io/fusionauth/http/server/Configurable.java b/src/main/java/io/fusionauth/http/server/Configurable.java index 43ca189..b745705 100644 --- a/src/main/java/io/fusionauth/http/server/Configurable.java +++ b/src/main/java/io/fusionauth/http/server/Configurable.java @@ -333,8 +333,12 @@ default T withShutdownDuration(Duration duration) { /** * - * Sets the unexpected exception handler that may occur while processing an HTTP request. This can be set to null which means the HTTP - * worker will use the default behavior. + * Sets the unexpected exception handler. This handler will be called when an unexpected exception is taken while processing the HTTP + * request by the HTTP worker. + *

+ * This allows you to customize the status code and logging behavior. + *

+ * Must not be null. * * @param unexpectedExceptionHandler The unexpected exception handler. * @return This. diff --git a/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java b/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java new file mode 100644 index 0000000..e154202 --- /dev/null +++ b/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.http.server; + +import io.fusionauth.http.log.Logger; + +/** + * THe default HTTP unexpected exception handler. + * + * @author Daniel DeGroff + */ +public class DefaultHTTPUnexpectedExceptionHandler implements HTTPUnexpectedExceptionHandler { + + @Override + public int handle(Logger logger, Throwable t) { + int internalServerError = 500; + logger.error(String.format("[%s] Closing socket with status [%d]. An HTTP worker threw an exception while processing a request.", Thread.currentThread().threadId(), internalServerError), t); + return internalServerError; + } +} diff --git a/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java b/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java index 09b6bc1..fd9153f 100644 --- a/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java +++ b/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java @@ -79,7 +79,7 @@ public class HTTPServerConfiguration implements Configurable + * The intent is that this provides additional flexibility on the status code and the logging behavior when an unexpected exception + * caught. + * + * @param logger the HTTP worker logger. + * @param t the unexpected exception to handle. + * @return the desired HTTP status code. Note that if the response has already been committed this will be ignored. */ - void handle(HTTPResponse response, Throwable t); + int handle(Logger logger, Throwable t); } diff --git a/src/main/java/io/fusionauth/http/server/internal/HTTPWorker.java b/src/main/java/io/fusionauth/http/server/internal/HTTPWorker.java index c26e891..e7ea030 100644 --- a/src/main/java/io/fusionauth/http/server/internal/HTTPWorker.java +++ b/src/main/java/io/fusionauth/http/server/internal/HTTPWorker.java @@ -36,7 +36,6 @@ import io.fusionauth.http.server.HTTPRequest; import io.fusionauth.http.server.HTTPResponse; import io.fusionauth.http.server.HTTPServerConfiguration; -import io.fusionauth.http.server.HTTPUnexpectedExceptionHandler; import io.fusionauth.http.server.Instrumenter; import io.fusionauth.http.server.io.ConnectionClosedException; import io.fusionauth.http.server.io.HTTPInputStream; @@ -268,24 +267,10 @@ public void run() { logger.debug(String.format("[%s] Closing socket with status [%d]. An IO exception was thrown during processing. These are pretty common.", Thread.currentThread().threadId(), Status.InternalServerError), e); closeSocketOnError(response, Status.InternalServerError); } catch (Throwable e) { - HTTPUnexpectedExceptionHandler unexpectedExceptionHandler = configuration.getDefaultExceptionHandler(); var status = Status.InternalServerError; - if (unexpectedExceptionHandler != null) { - if (response != null) { - // Set the initial status, allowing the handler to attempt to modify the status code. - response.setHeader(Headers.Connection, Connections.Close); - response.setStatus(status); - } - - try { - unexpectedExceptionHandler.handle(response, e); - } catch (Throwable ignore) { - } - - status = response != null ? response.getStatus() : status; - } else { - // Log the error - logger.error(String.format("[%s] Closing socket with status [%d]. An HTTP worker threw an exception while processing a request.", Thread.currentThread().threadId(), status), e); + try { + status = configuration.getUnexpectedExceptionHandler().handle(logger, e); + } catch (Throwable ignore) { } // Signal an error From 64bfa6f5666b515ebd926298ec58be4a0570ddaf Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Mon, 25 Aug 2025 22:23:15 -0600 Subject: [PATCH 03/10] copy updates and formatting for consistency. --- .../http/server/HTTPServerConfiguration.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java b/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java index fd9153f..2a1e6c2 100644 --- a/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java +++ b/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java @@ -278,7 +278,7 @@ public Duration getShutdownDuration() { } /** - * @return The HTTP unexpected exception handler for this server or null if a default was not set. + * @return The HTTP unexpected exception handler for this server. Never null. */ public HTTPUnexpectedExceptionHandler getUnexpectedExceptionHandler() { return unexpectedExceptionHandler; @@ -343,7 +343,7 @@ public HTTPServerConfiguration withContextPath(String contextPath) { */ @Override public HTTPServerConfiguration withExpectValidator(ExpectValidator validator) { - Objects.requireNonNull(handler, "You cannot set ExpectValidator to null"); + Objects.requireNonNull(validator, "You cannot set the expect validator to null"); this.expectValidator = validator; return this; } @@ -353,7 +353,7 @@ public HTTPServerConfiguration withExpectValidator(ExpectValidator validator) { */ @Override public HTTPServerConfiguration withHandler(HTTPHandler handler) { - Objects.requireNonNull(handler, "You cannot set HTTPHandler to null"); + Objects.requireNonNull(handler, "You cannot set the handler to null"); this.handler = handler; return this; } @@ -363,7 +363,7 @@ public HTTPServerConfiguration withHandler(HTTPHandler handler) { */ @Override public HTTPServerConfiguration withInitialReadTimeout(Duration duration) { - Objects.requireNonNull(duration, "You cannot set the client timeout to null"); + Objects.requireNonNull(duration, "You cannot set the client read timeout duration to null"); if (duration.isZero() || duration.isNegative()) { throw new IllegalArgumentException("The client timeout duration must be greater than 0"); } @@ -388,7 +388,6 @@ public HTTPServerConfiguration withInstrumenter(Instrumenter instrumenter) { @Override public HTTPServerConfiguration withKeepAliveTimeoutDuration(Duration duration) { Objects.requireNonNull(duration, "You cannot set the keep-alive timeout duration to null"); - if (duration.isZero() || duration.isNegative()) { throw new IllegalArgumentException("The keep-alive timeout duration must be grater than 0"); } @@ -402,7 +401,7 @@ public HTTPServerConfiguration withKeepAliveTimeoutDuration(Duration duration) { */ @Override public HTTPServerConfiguration withListener(HTTPListenerConfiguration listener) { - Objects.requireNonNull(listener, "You cannot set HTTPListenerConfiguration to null"); + Objects.requireNonNull(listener, "You cannot add a null HTTPListenerConfiguration"); this.listeners.add(listener); return this; } @@ -412,7 +411,7 @@ public HTTPServerConfiguration withListener(HTTPListenerConfiguration listener) */ @Override public HTTPServerConfiguration withLoggerFactory(LoggerFactory loggerFactory) { - Objects.requireNonNull(loggerFactory, "You cannot set LoggerFactory to null"); + Objects.requireNonNull(loggerFactory, "You cannot set the logger factory to null"); this.loggerFactory = loggerFactory; return this; } @@ -506,7 +505,6 @@ public HTTPServerConfiguration withMultipartBufferSize(int multipartBufferSize) @Override public HTTPServerConfiguration withMultipartConfiguration(MultipartConfiguration multipartStreamConfiguration) { Objects.requireNonNull(multipartStreamConfiguration, "You cannot set the multipart stream configuration to null"); - this.multipartStreamConfiguration = multipartStreamConfiguration; return this; } @@ -517,7 +515,6 @@ public HTTPServerConfiguration withMultipartConfiguration(MultipartConfiguration @Override public HTTPServerConfiguration withProcessingTimeoutDuration(Duration duration) { Objects.requireNonNull(duration, "You cannot set the processing timeout duration to null"); - if (duration.isZero() || duration.isNegative()) { throw new IllegalArgumentException("The processing timeout duration must be grater than 0"); } @@ -532,7 +529,6 @@ public HTTPServerConfiguration withProcessingTimeoutDuration(Duration duration) @Override public HTTPServerConfiguration withReadThroughputCalculationDelayDuration(Duration duration) { Objects.requireNonNull(duration, "You cannot set the read throughput delay duration to null"); - if (duration.isZero() || duration.isNegative()) { throw new IllegalArgumentException("The read throughput delay duration must be grater than 0"); } @@ -573,7 +569,6 @@ public HTTPServerConfiguration withResponseBufferSize(int responseBufferSize) { @Override public HTTPServerConfiguration withShutdownDuration(Duration duration) { Objects.requireNonNull(duration, "You cannot set the shutdown duration to null"); - if (duration.isZero() || duration.isNegative()) { throw new IllegalArgumentException("The shutdown duration must be grater than 0"); } @@ -587,6 +582,7 @@ public HTTPServerConfiguration withShutdownDuration(Duration duration) { */ @Override public HTTPServerConfiguration withUnexpectedExceptionHandler(HTTPUnexpectedExceptionHandler unexpectedExceptionHandler) { + Objects.requireNonNull(unexpectedExceptionHandler, "You cannot set the unexpected exception handler to null"); this.unexpectedExceptionHandler = unexpectedExceptionHandler; return this; } @@ -597,7 +593,6 @@ public HTTPServerConfiguration withUnexpectedExceptionHandler(HTTPUnexpectedExce @Override public HTTPServerConfiguration withWriteThroughputCalculationDelayDuration(Duration duration) { Objects.requireNonNull(duration, "You cannot set the write throughput delay duration to null"); - if (duration.isZero() || duration.isNegative()) { throw new IllegalArgumentException("The write throughput delay duration must be grater than 0"); } From 7f0c1a6b0e4319e29b53f79204821e96e5193dde Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Mon, 25 Aug 2025 22:43:57 -0600 Subject: [PATCH 04/10] pass in a logger factory --- .../server/DefaultHTTPUnexpectedExceptionHandler.java | 4 +++- .../http/server/HTTPUnexpectedExceptionHandler.java | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java b/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java index e154202..7d23aa1 100644 --- a/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java +++ b/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java @@ -16,6 +16,7 @@ package io.fusionauth.http.server; import io.fusionauth.http.log.Logger; +import io.fusionauth.http.log.LoggerFactory; /** * THe default HTTP unexpected exception handler. @@ -25,8 +26,9 @@ public class DefaultHTTPUnexpectedExceptionHandler implements HTTPUnexpectedExceptionHandler { @Override - public int handle(Logger logger, Throwable t) { + public int handle(LoggerFactory loggerFactory, Throwable t) { int internalServerError = 500; + Logger logger = loggerFactory.getLogger(DefaultHTTPUnexpectedExceptionHandler.class); logger.error(String.format("[%s] Closing socket with status [%d]. An HTTP worker threw an exception while processing a request.", Thread.currentThread().threadId(), internalServerError), t); return internalServerError; } diff --git a/src/main/java/io/fusionauth/http/server/HTTPUnexpectedExceptionHandler.java b/src/main/java/io/fusionauth/http/server/HTTPUnexpectedExceptionHandler.java index fc16426..ab5a1a6 100644 --- a/src/main/java/io/fusionauth/http/server/HTTPUnexpectedExceptionHandler.java +++ b/src/main/java/io/fusionauth/http/server/HTTPUnexpectedExceptionHandler.java @@ -15,7 +15,7 @@ */ package io.fusionauth.http.server; -import io.fusionauth.http.log.Logger; +import io.fusionauth.http.log.LoggerFactory; /** * An interface defining the HTTP unexpected exception handler contract. @@ -30,9 +30,9 @@ public interface HTTPUnexpectedExceptionHandler { * The intent is that this provides additional flexibility on the status code and the logging behavior when an unexpected exception * caught. * - * @param logger the HTTP worker logger. - * @param t the unexpected exception to handle. + * @param loggerFactory the configured logger factory. + * @param t the unexpected exception to handle. * @return the desired HTTP status code. Note that if the response has already been committed this will be ignored. */ - int handle(Logger logger, Throwable t); + int handle(LoggerFactory loggerFactory, Throwable t); } From 475e3a182d5a36a14355973e3d762df47678d974 Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Mon, 25 Aug 2025 22:59:26 -0600 Subject: [PATCH 05/10] re-factor to decouple logging better. --- .../server/DefaultHTTPUnexpectedExceptionHandler.java | 8 ++++++-- .../fusionauth/http/server/HTTPServerConfiguration.java | 7 ++++++- .../http/server/HTTPUnexpectedExceptionHandler.java | 7 ++----- .../io/fusionauth/http/server/internal/HTTPWorker.java | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java b/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java index 7d23aa1..409fbe8 100644 --- a/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java +++ b/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java @@ -24,11 +24,15 @@ * @author Daniel DeGroff */ public class DefaultHTTPUnexpectedExceptionHandler implements HTTPUnexpectedExceptionHandler { + private final Logger logger; + + public DefaultHTTPUnexpectedExceptionHandler(LoggerFactory loggerFactory) { + this.logger = loggerFactory.getLogger(DefaultHTTPUnexpectedExceptionHandler.class); + } @Override - public int handle(LoggerFactory loggerFactory, Throwable t) { + public int handle(Throwable t) { int internalServerError = 500; - Logger logger = loggerFactory.getLogger(DefaultHTTPUnexpectedExceptionHandler.class); logger.error(String.format("[%s] Closing socket with status [%d]. An HTTP worker threw an exception while processing a request.", Thread.currentThread().threadId(), internalServerError), t); return internalServerError; } diff --git a/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java b/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java index 2a1e6c2..38b1b04 100644 --- a/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java +++ b/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java @@ -79,7 +79,7 @@ public class HTTPServerConfiguration implements Configurable Date: Mon, 25 Aug 2025 23:07:31 -0600 Subject: [PATCH 06/10] fix tests to ensure a null expectValidator is not set. --- src/test/java/io/fusionauth/http/BaseTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/fusionauth/http/BaseTest.java b/src/test/java/io/fusionauth/http/BaseTest.java index 26fe0e3..447bbed 100644 --- a/src/test/java/io/fusionauth/http/BaseTest.java +++ b/src/test/java/io/fusionauth/http/BaseTest.java @@ -58,6 +58,7 @@ import io.fusionauth.http.log.Level; import io.fusionauth.http.log.LoggerFactory; import io.fusionauth.http.security.SecurityTools; +import io.fusionauth.http.server.AlwaysContinueExpectValidator; import io.fusionauth.http.server.ExpectValidator; import io.fusionauth.http.server.HTTPHandler; import io.fusionauth.http.server.HTTPListenerConfiguration; @@ -297,7 +298,7 @@ public HTTPServer makeServer(String scheme, HTTPHandler handler, Instrumenter in .withKeepAliveTimeoutDuration(ServerTimeout) .withInitialReadTimeout(ServerTimeout) .withProcessingTimeoutDuration(ServerTimeout) - .withExpectValidator(expectValidator) + .withExpectValidator(expectValidator != null ? expectValidator : new AlwaysContinueExpectValidator()) .withInstrumenter(instrumenter) .withLoggerFactory(factory) .withMinimumReadThroughput(200 * 1024) From 797caa38d5832d43d9c19fb7d79230d2d2ea6ff3 Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Mon, 25 Aug 2025 23:10:04 -0600 Subject: [PATCH 07/10] pom --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d334b5e..426fdfa 100644 --- a/pom.xml +++ b/pom.xml @@ -200,4 +200,4 @@ - + \ No newline at end of file From a629e0f0cd9aa2d37472a358e53f3deeab28b668 Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Thu, 28 Aug 2025 09:45:38 -0600 Subject: [PATCH 08/10] Pass in a context with an optional logger and request object for additional context for the handler. --- ...DefaultHTTPUnexpectedExceptionHandler.java | 15 +------ .../http/server/ExceptionHandlerContext.java | 40 +++++++++++++++++++ .../http/server/HTTPServerConfiguration.java | 7 +--- .../HTTPUnexpectedExceptionHandler.java | 5 +-- .../http/server/internal/HTTPWorker.java | 10 +++-- 5 files changed, 51 insertions(+), 26 deletions(-) create mode 100644 src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java diff --git a/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java b/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java index 409fbe8..e538e16 100644 --- a/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java +++ b/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java @@ -15,25 +15,14 @@ */ package io.fusionauth.http.server; -import io.fusionauth.http.log.Logger; -import io.fusionauth.http.log.LoggerFactory; - /** * THe default HTTP unexpected exception handler. * * @author Daniel DeGroff */ public class DefaultHTTPUnexpectedExceptionHandler implements HTTPUnexpectedExceptionHandler { - private final Logger logger; - - public DefaultHTTPUnexpectedExceptionHandler(LoggerFactory loggerFactory) { - this.logger = loggerFactory.getLogger(DefaultHTTPUnexpectedExceptionHandler.class); - } - @Override - public int handle(Throwable t) { - int internalServerError = 500; - logger.error(String.format("[%s] Closing socket with status [%d]. An HTTP worker threw an exception while processing a request.", Thread.currentThread().threadId(), internalServerError), t); - return internalServerError; + public void handle(ExceptionHandlerContext context) { + context.logger.error(String.format("[%s] Closing socket with status [%d]. An HTTP worker threw an exception while processing a request.", Thread.currentThread().threadId(), context.statusCode), context.throwable); } } diff --git a/src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java b/src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java new file mode 100644 index 0000000..b0d84bf --- /dev/null +++ b/src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.http.server; + +import io.fusionauth.http.log.Logger; + +/** + * Provide context to the exception handler. + * + * @author Daniel DeGroff + */ +public class ExceptionHandlerContext { + public Logger logger; + + public HTTPRequest request; + + public int statusCode; + + public Throwable throwable; + + public ExceptionHandlerContext(Logger logger, HTTPRequest request, int statusCode, Throwable throwable) { + this.logger = logger; + this.request = request; // may be null + this.statusCode = statusCode; + this.throwable = throwable; + } +} diff --git a/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java b/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java index 38b1b04..2a1e6c2 100644 --- a/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java +++ b/src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java @@ -79,7 +79,7 @@ public class HTTPServerConfiguration implements Configurable Date: Thu, 28 Aug 2025 09:56:05 -0600 Subject: [PATCH 09/10] Add mutators to the context to make it more obvious to the user what is modifiable and what is not. --- ...DefaultHTTPUnexpectedExceptionHandler.java | 6 +- .../http/server/ExceptionHandlerContext.java | 56 +++++++++++++++++-- .../http/server/internal/HTTPWorker.java | 2 +- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java b/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java index e538e16..e6db6e6 100644 --- a/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java +++ b/src/main/java/io/fusionauth/http/server/DefaultHTTPUnexpectedExceptionHandler.java @@ -23,6 +23,10 @@ public class DefaultHTTPUnexpectedExceptionHandler implements HTTPUnexpectedExceptionHandler { @Override public void handle(ExceptionHandlerContext context) { - context.logger.error(String.format("[%s] Closing socket with status [%d]. An HTTP worker threw an exception while processing a request.", Thread.currentThread().threadId(), context.statusCode), context.throwable); + context.getLogger() + .error(String.format("[%s] Closing socket with status [%d]. An HTTP worker threw an exception while processing a request.", + Thread.currentThread().threadId(), + context.getStatusCode()), + context.getThrowable()); } } diff --git a/src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java b/src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java index b0d84bf..bbb58fc 100644 --- a/src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java +++ b/src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java @@ -23,18 +23,64 @@ * @author Daniel DeGroff */ public class ExceptionHandlerContext { - public Logger logger; + private final Logger logger; - public HTTPRequest request; + private final HTTPRequest request; - public int statusCode; + private final Throwable throwable; - public Throwable throwable; + private int statusCode; public ExceptionHandlerContext(Logger logger, HTTPRequest request, int statusCode, Throwable throwable) { this.logger = logger; - this.request = request; // may be null + this.request = request; this.statusCode = statusCode; this.throwable = throwable; } + + /** + * This is provided for convenience, but you may wish to use your own logger. + * + * @return the optional logger to use in the exception handler. + */ + public Logger getLogger() { + return logger; + } + + /** + * This may be useful if you wish to know additional context of the exception such as the URI of the current HTTP request. + *

+ * Modifications to this object will have no effect on current or futures requests. + * + * @return the current HTTP request, or null if this exception was taking prior to constructing the HTTP request. This is unlikely but + * please account for this value being null. + */ + public HTTPRequest getRequest() { + return request; + } + + /** + * @return the desired status code for the HTTP response. + */ + public int getStatusCode() { + return statusCode; + } + + /** + * Suggest a status code for the HTTP response. This value will be used unless the response has already been committed meaning bytes have + * already been written to the client and the HTTP server is not able to modify the response code. + * + * @param statusCode the desired status code to set on the HTTP response. + */ + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + /** + * + * @return the unexpected exception to handle + */ + public Throwable getThrowable() { + return throwable; + } } diff --git a/src/main/java/io/fusionauth/http/server/internal/HTTPWorker.java b/src/main/java/io/fusionauth/http/server/internal/HTTPWorker.java index b022d46..1b150e8 100644 --- a/src/main/java/io/fusionauth/http/server/internal/HTTPWorker.java +++ b/src/main/java/io/fusionauth/http/server/internal/HTTPWorker.java @@ -277,7 +277,7 @@ public void run() { } // Signal an error - closeSocketOnError(response, context.statusCode); + closeSocketOnError(response, context.getStatusCode()); } finally { if (instrumenter != null) { instrumenter.workerStopped(); From 4d90578571b9d7671ffb2eef3ed2d7e00e1ab4cd Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Thu, 28 Aug 2025 09:58:53 -0600 Subject: [PATCH 10/10] cleanup --- .../java/io/fusionauth/http/server/ExceptionHandlerContext.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java b/src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java index bbb58fc..894cf42 100644 --- a/src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java +++ b/src/main/java/io/fusionauth/http/server/ExceptionHandlerContext.java @@ -77,7 +77,6 @@ public void setStatusCode(int statusCode) { } /** - * * @return the unexpected exception to handle */ public Throwable getThrowable() {