diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
index 2bde850047b..eb0f21e7e65 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
@@ -55,6 +55,7 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
private final LongAdder _expires = new LongAdder();
private final LongAdder _errors = new LongAdder();
+ private final LongAdder _responsesThrown = new LongAdder();
private final LongAdder _responses1xx = new LongAdder();
private final LongAdder _responses2xx = new LongAdder();
private final LongAdder _responses3xx = new LongAdder();
@@ -91,7 +92,7 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
long elapsed = System.currentTimeMillis() - request.getTimeStamp();
_requestStats.decrement();
_requestTimeStats.record(elapsed);
- updateResponse(request);
+ updateResponse(request, false);
_asyncWaitStats.decrement();
if (_shutdown.isShutdown())
@@ -166,10 +167,16 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
_asyncDispatches.increment();
}
+ boolean thrownError = false;
try
{
handler.handle(path, baseRequest, request, response);
}
+ catch (Throwable t)
+ {
+ thrownError = true;
+ throw t;
+ }
finally
{
final long now = System.currentTimeMillis();
@@ -189,7 +196,7 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
{
_requestStats.decrement();
_requestTimeStats.record(dispatched);
- updateResponse(baseRequest);
+ updateResponse(baseRequest, thrownError);
}
}
@@ -198,10 +205,14 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
}
}
- protected void updateResponse(Request request)
+ protected void updateResponse(Request request, boolean thrownError)
{
Response response = request.getResponse();
- if (request.isHandled())
+ if (thrownError)
+ {
+ _responsesThrown.increment();
+ }
+ else if (request.isHandled())
{
switch (response.getStatus() / 100)
{
@@ -537,6 +548,18 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
return _responses5xx.intValue();
}
+ /**
+ * @return the number of requests that threw an exception during handling
+ * since {@link #statsReset()} was last called. These may have resulted in
+ * some error responses which were unrecorded by the {@link StatisticsHandler}.
+ */
+ @ManagedAttribute("number of requests that threw an exception during handling")
+ public int getResponsesThrown()
+ {
+ return _responsesThrown.intValue();
+ }
+
+
/**
* @return the milliseconds since the statistics were started with {@link #statsReset()}.
*/
@@ -590,6 +613,7 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
sb.append("3xx responses: ").append(getResponses3xx()).append("
\n");
sb.append("4xx responses: ").append(getResponses4xx()).append("
\n");
sb.append("5xx responses: ").append(getResponses5xx()).append("
\n");
+ sb.append("responses thrown: ").append(getResponsesThrown()).append("
\n");
sb.append("Bytes sent total: ").append(getResponsesBytesTotal()).append("
\n");
return sb.toString();
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
index d36d15e1f40..b9a3da5d842 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
@@ -30,6 +30,8 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.ConnectionStatistics;
+import org.eclipse.jetty.logging.StacklessLogging;
+import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
@@ -38,6 +40,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -420,6 +423,60 @@ public class StatisticsHandlerTest
barrier[3].await();
}
+ @Test
+ public void testThrownResponse() throws Exception
+ {
+ _statsHandler.setHandler(new AbstractHandler()
+ {
+ @Override
+ public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException
+ {
+ try
+ {
+ throw new IllegalStateException("expected");
+ }
+ catch (IllegalStateException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new IOException(e);
+ }
+ }
+ });
+ _server.start();
+
+ try (StacklessLogging ignored = new StacklessLogging(HttpChannel.class))
+ {
+ String request = "GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n";
+ String response = _connector.getResponse(request);
+ assertThat(response, containsString("HTTP/1.1 500 Server Error"));
+ }
+
+ assertEquals(1, _statsHandler.getRequests());
+ assertEquals(0, _statsHandler.getRequestsActive());
+ assertEquals(1, _statsHandler.getRequestsActiveMax());
+
+ assertEquals(1, _statsHandler.getDispatched());
+ assertEquals(0, _statsHandler.getDispatchedActive());
+ assertEquals(1, _statsHandler.getDispatchedActiveMax());
+
+ assertEquals(0, _statsHandler.getAsyncRequests());
+ assertEquals(0, _statsHandler.getAsyncDispatches());
+ assertEquals(0, _statsHandler.getExpires());
+
+ // We get no recorded status, but we get a recorded thrown response.
+ assertEquals(0, _statsHandler.getResponses1xx());
+ assertEquals(0, _statsHandler.getResponses2xx());
+ assertEquals(0, _statsHandler.getResponses3xx());
+ assertEquals(0, _statsHandler.getResponses4xx());
+ assertEquals(0, _statsHandler.getResponses5xx());
+ assertEquals(1, _statsHandler.getResponsesThrown());
+ }
+
@Test
public void waitForSuspendedRequestTest() throws Exception
{