From 09b0222f0288f293764ef7dcfd2904f0ff20c739 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 3 Nov 2021 15:06:35 +1100 Subject: [PATCH] Add connection failure listener This allows applications to react to the client closing the connection in a timely manner, without needing to perform IO to be notified. Signed-off-by: Stuart Douglas --- .../servlet/ConnectionFailureDetails.java | 28 +++++++++++++++++++ .../servlet/ConnectionFailureListener.java | 22 +++++++++++++++ .../java/jakarta/servlet/ServletRequest.java | 14 ++++++++++ .../servlet/ServletRequestWrapper.java | 16 +++++++++++ .../jakarta/servlet/MockServletRequest.java | 5 ++++ 5 files changed, 85 insertions(+) create mode 100644 api/src/main/java/jakarta/servlet/ConnectionFailureDetails.java create mode 100644 api/src/main/java/jakarta/servlet/ConnectionFailureListener.java diff --git a/api/src/main/java/jakarta/servlet/ConnectionFailureDetails.java b/api/src/main/java/jakarta/servlet/ConnectionFailureDetails.java new file mode 100644 index 000000000..bfd533f95 --- /dev/null +++ b/api/src/main/java/jakarta/servlet/ConnectionFailureDetails.java @@ -0,0 +1,28 @@ +package jakarta.servlet; + +import java.io.IOException; +import java.util.Optional; + +/** + * An interface that carries information about why a connection has failed. + * + * @since Servlet 6.0 + */ +public interface ConnectionFailureDetails { + + /** + * Returns an optional cause of the underlying failure. If the failure was caused by a protocol level mechanism rather + * than a failure then this may be missing. + * + * @return The cause of the failure + */ + Optional cause(); + + /** + * If this is {@code true} the stream was explicitly reset by the remote endpoint, if this is false then there has been + * a connection level failure rather than the client simply closing this stream. + * + * @return {@code true} if the stream was explicitly reset by the remote endpoint + */ + boolean isStreamReset(); +} diff --git a/api/src/main/java/jakarta/servlet/ConnectionFailureListener.java b/api/src/main/java/jakarta/servlet/ConnectionFailureListener.java new file mode 100644 index 000000000..d74b85492 --- /dev/null +++ b/api/src/main/java/jakarta/servlet/ConnectionFailureListener.java @@ -0,0 +1,22 @@ +package jakarta.servlet; + +import java.util.EventListener; + +/** + * A listener that allows an application to be notified of a problem with the underlying connection. + * + * @since Servlet 6.0 + */ +public interface ConnectionFailureListener extends EventListener { + + /** + * This method is invoked on a best effort basis if the underlying connection has gone away. + * + * This method will generally be invoked from a different thread to any other threads currently running in the + * application, so any attempt to stop application processing as a result of this notification should be done in a + * thread safe manner. + * + * @param details Information about the failure + */ + void onConnectionFailure(ConnectionFailureDetails details); +} diff --git a/api/src/main/java/jakarta/servlet/ServletRequest.java b/api/src/main/java/jakarta/servlet/ServletRequest.java index 8c57548ec..5113fb10c 100644 --- a/api/src/main/java/jakarta/servlet/ServletRequest.java +++ b/api/src/main/java/jakarta/servlet/ServletRequest.java @@ -609,4 +609,18 @@ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse se * @since Servlet 6.0 */ ServletConnection getServletConnection(); + + /** + * Adds a listener to be eagerly notified of any underlying connection failure. Delivery to this listener is on a best + * effort basis, depending on the implementation and the underlying protocol in use some containers may never be able to + * warn of underlying connection failure. + * + * This listener is intended to allow long running tasks to potentially be cancelled if the underlying connection has + * gone away. Failures will still be reported at the IO level if an attempt is made to read or write to the request or + * response, and if async IO is in use failures may also be delivered to these listeners. + * + * @since Servlet 6.0 + * @param listener The listener to be notified. + */ + void addConnectionFailureListener(ConnectionFailureListener listener); } diff --git a/api/src/main/java/jakarta/servlet/ServletRequestWrapper.java b/api/src/main/java/jakarta/servlet/ServletRequestWrapper.java index 122cbb795..88c558a05 100644 --- a/api/src/main/java/jakarta/servlet/ServletRequestWrapper.java +++ b/api/src/main/java/jakarta/servlet/ServletRequestWrapper.java @@ -515,4 +515,20 @@ public String getProtocolRequestId() { public ServletConnection getServletConnection() { return request.getServletConnection(); } + + /** + * Adds a listener to be eagerly notified of any underlying connection failure. Delivery to this listener is on a best + * effort basis, depending on the implementation and the underlying protocol in use some containers may never be able to + * warn of underlying connection failure. + * + * This listener is intended to allow long running tasks to potentially be cancelled if the underlying connection has + * gone away. Failures will still be reported at the IO level if an attempt is made to read or write to the request or + * response, and if async IO is in use failures may also be delivered to these listeners. + * + * @param listener The listener to be notified. + */ + @Override + public void addConnectionFailureListener(ConnectionFailureListener listener) { + request.addConnectionFailureListener(listener); + } } diff --git a/api/src/test/java/jakarta/servlet/MockServletRequest.java b/api/src/test/java/jakarta/servlet/MockServletRequest.java index fad571ecf..55ce011f7 100644 --- a/api/src/test/java/jakarta/servlet/MockServletRequest.java +++ b/api/src/test/java/jakarta/servlet/MockServletRequest.java @@ -226,4 +226,9 @@ public String getProtocolRequestId() { public ServletConnection getServletConnection() { return null; } + + @Override + public void addConnectionFailureListener(ConnectionFailureListener listener) { + + } }