Skip to content

Commit 69df407

Browse files
committed
Allow an exception handler to be bound to handle unexpected for the HTTP worker
1 parent 967a5d1 commit 69df407

File tree

6 files changed

+81
-5
lines changed

6 files changed

+81
-5
lines changed

build.savant

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ restifyVersion = "4.2.1"
1818
slf4jVersion = "2.0.17"
1919
testngVersion = "7.11.0"
2020

21-
project(group: "io.fusionauth", name: "java-http", version: "1.1.1", licenses: ["ApacheV2_0"]) {
21+
project(group: "io.fusionauth", name: "java-http", version: "1.2.0", licenses: ["ApacheV2_0"]) {
2222
workflow {
2323
fetch {
2424
// Dependency resolution order:

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<modelVersion>4.0.0</modelVersion>
33
<groupId>io.fusionauth</groupId>
44
<artifactId>java-http</artifactId>
5-
<version>1.1.1</version>
5+
<version>1.2.0</version>
66
<packaging>jar</packaging>
77

88
<name>Java HTTP library (client and server)</name>
@@ -200,4 +200,4 @@
200200
</build>
201201
</profile>
202202
</profiles>
203-
</project>
203+
</project>

src/main/java/io/fusionauth/http/server/Configurable.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,19 @@ default T withShutdownDuration(Duration duration) {
331331
return (T) this;
332332
}
333333

334+
/**
335+
*
336+
* Sets the unexpected exception handler that may occur while processing an HTTP request. This can be set to null which means the HTTP
337+
* worker will use the default behavior.
338+
*
339+
* @param unexpectedExceptionHandler The unexpected exception handler.
340+
* @return This.
341+
*/
342+
default T withUnexpectedExceptionHandler(HTTPUnexpectedExceptionHandler unexpectedExceptionHandler) {
343+
configuration().withUnexpectedExceptionHandler(unexpectedExceptionHandler);
344+
return (T) this;
345+
}
346+
334347
/**
335348
* This configures the duration of the initial delay before calculating and enforcing the minimum write throughput. Defaults to 5
336349
* seconds.

src/main/java/io/fusionauth/http/server/HTTPServerConfiguration.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public class HTTPServerConfiguration implements Configurable<HTTPServerConfigura
7979

8080
private Duration shutdownDuration = Duration.ofSeconds(10);
8181

82+
private HTTPUnexpectedExceptionHandler unexpectedExceptionHandler;
83+
8284
private Duration writeThroughputCalculationDelayDuration = Duration.ofSeconds(5);
8385

8486
/**
@@ -110,6 +112,14 @@ public String getContextPath() {
110112
return contextPath;
111113
}
112114

115+
/**
116+
* @return The HTTP unexpected exception handler for this server or null if a default was not set.
117+
*/
118+
public HTTPUnexpectedExceptionHandler getDefaultExceptionHandler() {
119+
return unexpectedExceptionHandler;
120+
}
121+
122+
113123
/**
114124
* @return The expect validator. Cannot be null and is required.
115125
*/
@@ -573,6 +583,15 @@ public HTTPServerConfiguration withShutdownDuration(Duration duration) {
573583
return this;
574584
}
575585

586+
/**
587+
* {@inheritDoc}
588+
*/
589+
@Override
590+
public HTTPServerConfiguration withUnexpectedExceptionHandler(HTTPUnexpectedExceptionHandler unexpectedExceptionHandler) {
591+
this.unexpectedExceptionHandler = unexpectedExceptionHandler;
592+
return this;
593+
}
594+
576595
/**
577596
* {@inheritDoc}
578597
*/
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2025, FusionAuth, All Rights Reserved
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific
14+
* language governing permissions and limitations under the License.
15+
*/
16+
package io.fusionauth.http.server;
17+
18+
public interface HTTPUnexpectedExceptionHandler {
19+
/**
20+
*
21+
* @param response the HTTP response, may be null.
22+
* @param t the unexpected exception to handle.
23+
*/
24+
void handle(HTTPResponse response, Throwable t);
25+
}

src/main/java/io/fusionauth/http/server/internal/HTTPWorker.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import io.fusionauth.http.server.HTTPRequest;
3737
import io.fusionauth.http.server.HTTPResponse;
3838
import io.fusionauth.http.server.HTTPServerConfiguration;
39+
import io.fusionauth.http.server.HTTPUnexpectedExceptionHandler;
3940
import io.fusionauth.http.server.Instrumenter;
4041
import io.fusionauth.http.server.io.ConnectionClosedException;
4142
import io.fusionauth.http.server.io.HTTPInputStream;
@@ -267,9 +268,27 @@ public void run() {
267268
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);
268269
closeSocketOnError(response, Status.InternalServerError);
269270
} catch (Throwable e) {
270-
// Log the error and signal a failure
271+
HTTPUnexpectedExceptionHandler unexpectedExceptionHandler = configuration.getDefaultExceptionHandler();
271272
var status = Status.InternalServerError;
272-
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);
273+
if (unexpectedExceptionHandler != null) {
274+
if (response != null) {
275+
// Set the initial status, allowing the handler to attempt to modify the status code.
276+
response.setHeader(Headers.Connection, Connections.Close);
277+
response.setStatus(status);
278+
}
279+
280+
try {
281+
unexpectedExceptionHandler.handle(response, e);
282+
} catch (Throwable ignore) {
283+
}
284+
285+
status = response != null ? response.getStatus() : status;
286+
} else {
287+
// Log the error
288+
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);
289+
}
290+
291+
// Signal an error
273292
closeSocketOnError(response, status);
274293
} finally {
275294
if (instrumenter != null) {

0 commit comments

Comments
 (0)