From 4e588b5ce025fa6a728e914c328e240f32a675e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Nie=C3=9Fing?= Date: Tue, 30 Jan 2024 19:16:47 +0100 Subject: [PATCH 1/2] Move common metrics collection logic to AbstractInstrumentedHandler; fix bugs where metrics weren't collected --- metrics-jetty12-ee10/pom.xml | 1 + .../jetty12/ee10/InstrumentedEE10Handler.java | 138 ++++++++---------- .../jetty12/AbstractInstrumentedHandler.java | 57 +++++++- 3 files changed, 115 insertions(+), 81 deletions(-) diff --git a/metrics-jetty12-ee10/pom.xml b/metrics-jetty12-ee10/pom.xml index 90b2b482d6..d965a6bdb5 100644 --- a/metrics-jetty12-ee10/pom.xml +++ b/metrics-jetty12-ee10/pom.xml @@ -85,6 +85,7 @@ org.eclipse.jetty jetty-util + provided org.eclipse.jetty.ee10 diff --git a/metrics-jetty12-ee10/src/main/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10Handler.java b/metrics-jetty12-ee10/src/main/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10Handler.java index 8a65e82cab..d614d8b35e 100644 --- a/metrics-jetty12-ee10/src/main/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10Handler.java +++ b/metrics-jetty12-ee10/src/main/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10Handler.java @@ -1,24 +1,23 @@ package io.dropwizard.metrics.jetty12.ee10; +import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.annotation.ResponseMeteredLevel; import io.dropwizard.metrics.jetty12.AbstractInstrumentedHandler; import jakarta.servlet.AsyncEvent; import jakarta.servlet.AsyncListener; -import org.eclipse.jetty.ee10.servlet.AsyncContextState; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletRequestEvent; +import jakarta.servlet.ServletRequestListener; import org.eclipse.jetty.ee10.servlet.ServletApiRequest; -import org.eclipse.jetty.ee10.servlet.ServletApiResponse; import org.eclipse.jetty.ee10.servlet.ServletChannelState; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.ee10.servlet.ServletContextRequest; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; -import org.eclipse.jetty.util.Callback; import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import static com.codahale.metrics.annotation.ResponseMeteredLevel.COARSE; /** * A Jetty {@link Handler} which records various metrics about an underlying {@link Handler} @@ -28,7 +27,7 @@ * {@link org.eclipse.jetty.ee10.servlet.ServletContextHandler#insertHandler(Singleton)}. */ public class InstrumentedEE10Handler extends AbstractInstrumentedHandler { - private AsyncListener listener; + private AsyncDispatchesAwareServletRequestListener asyncDispatchesAwareServletRequestListener; /** * Create a new instrumented handler using a given metrics registry. @@ -36,7 +35,7 @@ public class InstrumentedEE10Handler extends AbstractInstrumentedHandler { * @param registry the registry for the metrics */ public InstrumentedEE10Handler(MetricRegistry registry) { - super(registry, null); + super(registry); } /** @@ -46,7 +45,7 @@ public InstrumentedEE10Handler(MetricRegistry registry) { * @param prefix the prefix to use for the metrics names */ public InstrumentedEE10Handler(MetricRegistry registry, String prefix) { - super(registry, prefix, COARSE); + super(registry, prefix); } /** @@ -63,8 +62,7 @@ public InstrumentedEE10Handler(MetricRegistry registry, String prefix, ResponseM @Override protected void doStart() throws Exception { super.doStart(); - - this.listener = new AsyncAttachingListener(); + asyncDispatchesAwareServletRequestListener = new AsyncDispatchesAwareServletRequestListener(getAsyncDispatches()); } @Override @@ -73,104 +71,84 @@ protected void doStop() throws Exception { } @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception { + protected void setupServletListeners(Request request, Response response) { ServletContextRequest servletContextRequest = Request.as(request, ServletContextRequest.class); - - // only handle servlet requests with the InstrumentedHandler - // because it depends on the ServletRequestState if (servletContextRequest == null) { - return super.handle(request, response, callback); - } - - activeDispatches.inc(); - - final long start; - final ServletChannelState state = servletContextRequest.getServletRequestState(); - if (state.isInitial()) { - // new request - activeRequests.inc(); - start = Request.getTimeStamp(request); - state.addListener(listener); - } else { - // resumed request - start = System.currentTimeMillis(); - activeSuspended.dec(); - if (state.getState() == ServletChannelState.State.HANDLING) { - asyncDispatches.mark(); - } + return; } - boolean handled = false; + ServletChannelState servletChannelState = servletContextRequest.getServletRequestState(); + // the ServletChannelState gets recycled after handling, so add a new listener for every request + servletChannelState.addListener(new InstrumentedAsyncListener(getAsyncTimeouts())); - try { - handled = super.handle(request, response, callback); - } finally { - final long now = System.currentTimeMillis(); - final long dispatched = now - start; + ServletContextHandler servletContextHandler = servletContextRequest.getServletContextHandler(); + // addEventListener checks for duplicates, so we can try to add the listener for every request + servletContextHandler.addEventListener(asyncDispatchesAwareServletRequestListener); + } - activeDispatches.dec(); - dispatches.update(dispatched, TimeUnit.MILLISECONDS); + @Override + protected boolean isSuspended(Request request, Response response) { + ServletContextRequest servletContextRequest = Request.as(request, ServletContextRequest.class); + if (servletContextRequest == null) { + return false; + } - if (state.isSuspended()) { - activeSuspended.inc(); - } else if (state.isInitial()) { - updateResponses(request, response, start, handled); - } - // else onCompletion will handle it. + ServletChannelState servletChannelState = servletContextRequest.getServletRequestState(); + if (servletChannelState == null) { + return false; } - return handled; + return servletChannelState.isSuspended(); } - private class AsyncAttachingListener implements AsyncListener { - - @Override - public void onTimeout(AsyncEvent event) throws IOException {} + private static class AsyncDispatchesAwareServletRequestListener implements ServletRequestListener { + private final Meter asyncDispatches; - @Override - public void onStartAsync(AsyncEvent event) throws IOException { - event.getAsyncContext().addListener(new InstrumentedAsyncListener()); + private AsyncDispatchesAwareServletRequestListener(Meter asyncDispatches) { + this.asyncDispatches = asyncDispatches; } @Override - public void onError(AsyncEvent event) throws IOException {} + public void requestInitialized(ServletRequestEvent sre) { + ServletRequest servletRequest = sre.getServletRequest(); + if (!(servletRequest instanceof ServletApiRequest)) { + return; + } - @Override - public void onComplete(AsyncEvent event) throws IOException {} + ServletApiRequest servletApiRequest = (ServletApiRequest) servletRequest; + + ServletContextHandler.ServletRequestInfo servletRequestInfo = servletApiRequest.getServletRequestInfo(); + + ServletChannelState servletChannelState = servletRequestInfo.getServletRequestState(); + + // if the request isn't 'initial', the request was re-dispatched + if (servletChannelState.isAsync() && !servletChannelState.isInitial()) { + asyncDispatches.mark(); + } + } } - private class InstrumentedAsyncListener implements AsyncListener { - private final long startTime; + private static class InstrumentedAsyncListener implements AsyncListener { + private final Meter asyncTimeouts; - InstrumentedAsyncListener() { - this.startTime = System.currentTimeMillis(); + private InstrumentedAsyncListener(Meter asyncTimeouts) { + this.asyncTimeouts = asyncTimeouts; } @Override - public void onTimeout(AsyncEvent event) throws IOException { - asyncTimeouts.mark(); - } + public void onComplete(AsyncEvent event) throws IOException {} @Override - public void onStartAsync(AsyncEvent event) throws IOException { + public void onTimeout(AsyncEvent event) throws IOException { + asyncTimeouts.mark(); } @Override - public void onError(AsyncEvent event) throws IOException { - } + public void onError(AsyncEvent event) throws IOException {} @Override - public void onComplete(AsyncEvent event) throws IOException { - final AsyncContextState state = (AsyncContextState) event.getAsyncContext(); - final ServletApiRequest request = (ServletApiRequest) state.getRequest(); - final ServletApiResponse response = (ServletApiResponse) state.getResponse(); - updateResponses(request.getRequest(), response.getResponse(), startTime, true); - - final ServletContextRequest servletContextRequest = Request.as(request.getRequest(), ServletContextRequest.class); - final ServletChannelState servletRequestState = servletContextRequest.getServletRequestState(); - if (!servletRequestState.isSuspended()) { - activeSuspended.dec(); - } + public void onStartAsync(AsyncEvent event) throws IOException { + event.getAsyncContext().addListener(this); } } } diff --git a/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/AbstractInstrumentedHandler.java b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/AbstractInstrumentedHandler.java index d2a7899623..b83f92cb05 100644 --- a/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/AbstractInstrumentedHandler.java +++ b/metrics-jetty12/src/main/java/io/dropwizard/metrics/jetty12/AbstractInstrumentedHandler.java @@ -10,6 +10,7 @@ import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import java.util.Arrays; import java.util.Collections; @@ -19,6 +20,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import static com.codahale.metrics.MetricRegistry.name; import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL; @@ -273,6 +275,47 @@ protected void doStop() throws Exception { super.doStop(); } + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + activeDispatches.inc(); + activeRequests.inc(); + final long start = Request.getTimeStamp(request); + + final AtomicBoolean suspended = new AtomicBoolean(false); + + final Runnable metricUpdater = () -> { + updateResponses(request, response, start, true); + if (suspended.get()) { + activeSuspended.dec(); + } + }; + + final Callback metricUpdaterCallback = Callback.from(callback, metricUpdater); + boolean handled = false; + + setupServletListeners(request, response); + + try { + handled = super.handle(request, response, metricUpdaterCallback); + } finally { + final long now = System.currentTimeMillis(); + final long dispatched = now - start; + + activeDispatches.dec(); + dispatches.update(dispatched, TimeUnit.MILLISECONDS); + + if (isSuspended(request, response) && suspended.compareAndSet(false, true)) { + activeSuspended.inc(); + } + + if (!handled) { + updateResponses(request, response, start, false); + } + } + + return handled; + } + protected Timer requestTimer(String method) { final HttpMethod m = HttpMethod.fromString(method); if (m == null) { @@ -307,7 +350,7 @@ protected void updateResponses(Request request, Response response, long start, b if (isHandled) { mark(response.getStatus()); } else { - mark(404);; // will end up with a 404 response sent by HttpChannel.handle + mark(404); // will end up with a 404 response sent by HttpChannel.handle } activeRequests.dec(); final long elapsedTime = System.currentTimeMillis() - start; @@ -337,4 +380,16 @@ protected Meter getResponseCodeMeter(int statusCode) { protected String getMetricPrefix() { return this.prefix == null ? name(getHandler().getClass(), name) : name(this.prefix, name); } + + protected abstract void setupServletListeners(Request request, Response response); + + protected final Meter getAsyncDispatches() { + return asyncDispatches; + } + + protected final Meter getAsyncTimeouts() { + return asyncTimeouts; + } + + protected abstract boolean isSuspended(Request request, Response response); } From 80c084fd93e904abcd69ea4db383339d47e77732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Nie=C3=9Fing?= Date: Tue, 30 Jan 2024 19:30:19 +0100 Subject: [PATCH 2/2] Add further integration tests for InstrumentedEE10Handler metrics --- metrics-jetty12-ee10/pom.xml | 6 + .../jetty12/ee10/AbstractIntegrationTest.java | 50 +++++++++ .../metrics/jetty12/ee10/AsyncTest.java | 105 ++++++++++++++++++ .../ee10/InstrumentedEE10HandlerTest.java | 45 +------- .../jetty12/ee10/ResponseStatusTest.java | 60 ++++++++++ pom.xml | 1 + 6 files changed, 228 insertions(+), 39 deletions(-) create mode 100644 metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/AbstractIntegrationTest.java create mode 100644 metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/AsyncTest.java create mode 100644 metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/ResponseStatusTest.java diff --git a/metrics-jetty12-ee10/pom.xml b/metrics-jetty12-ee10/pom.xml index d965a6bdb5..4590196361 100644 --- a/metrics-jetty12-ee10/pom.xml +++ b/metrics-jetty12-ee10/pom.xml @@ -135,5 +135,11 @@ ${slf4j.version} test + + org.awaitility + awaitility + ${awaitility.version} + test + diff --git a/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/AbstractIntegrationTest.java b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/AbstractIntegrationTest.java new file mode 100644 index 0000000000..8ed2b83461 --- /dev/null +++ b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/AbstractIntegrationTest.java @@ -0,0 +1,50 @@ +package io.dropwizard.metrics.jetty12.ee10; + +import com.codahale.metrics.MetricRegistry; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.junit.After; +import org.junit.Before; + +import static com.codahale.metrics.annotation.ResponseMeteredLevel.ALL; + +abstract class AbstractIntegrationTest { + + protected final HttpClient client = new HttpClient(); + protected final MetricRegistry registry = new MetricRegistry(); + protected final Server server = new Server(); + protected final ServerConnector connector = new ServerConnector(server); + protected final InstrumentedEE10Handler handler = new InstrumentedEE10Handler(registry, null, ALL); + protected final ServletContextHandler servletContextHandler = new ServletContextHandler(); + + @Before + public void setUp() throws Exception { + handler.setName("handler"); + + // builds the following handler chain: + // ServletContextHandler -> InstrumentedHandler -> TestHandler + // the ServletContextHandler is needed to utilize servlet related classes + servletContextHandler.setHandler(getHandler()); + servletContextHandler.insertHandler(handler); + server.setHandler(servletContextHandler); + + server.addConnector(connector); + server.start(); + client.start(); + } + + @After + public void tearDown() throws Exception { + server.stop(); + client.stop(); + } + + protected String uri(String path) { + return "http://localhost:" + connector.getLocalPort() + path; + } + + protected abstract Handler getHandler(); +} diff --git a/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/AsyncTest.java b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/AsyncTest.java new file mode 100644 index 0000000000..3cae5e25f3 --- /dev/null +++ b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/AsyncTest.java @@ -0,0 +1,105 @@ +package io.dropwizard.metrics.jetty12.ee10; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.client.CompletableResponseListener; +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.client.Response; +import org.eclipse.jetty.ee10.servlet.ServletHandler; +import org.eclipse.jetty.server.Handler; +import org.junit.Test; + +import java.util.EnumSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.awaitility.Awaitility.await; + +public class AsyncTest extends AbstractIntegrationTest { + + @Override + protected Handler getHandler() { + return new ServletHandler(); + } + + @Test + public void testAsyncTimeout() throws Exception { + servletContextHandler.addFilter((request, response, chain) -> { + AsyncContext asyncContext = request.startAsync(); + asyncContext.setTimeout(1); + }, "/*", EnumSet.allOf(DispatcherType.class)); + + client.GET(uri("/")); + Meter asyncTimeouts = registry.meter(MetricRegistry.name(ServletHandler.class, "handler.async-timeouts")); + assertThat(asyncTimeouts.getCount()).isEqualTo(1L); + + client.GET(uri("/")); + assertThat(asyncTimeouts.getCount()).isEqualTo(2L); + } + + @Test + public void testActiveSuspended() { + servletContextHandler.addFilter((request, response, chain) -> { + AsyncContext asyncContext = request.startAsync(); + asyncContext.start(() -> { + try { + Thread.sleep(1000); + } catch (InterruptedException interruptedException) { + Thread.currentThread().interrupt(); + } + asyncContext.complete(); + }); + }, "/*", EnumSet.allOf(DispatcherType.class)); + + Counter activeSuspended = registry.counter(MetricRegistry.name(ServletHandler.class, "handler.active-suspended")); + Request request = client.POST(uri("/")); + CompletableResponseListener completableResponseListener = new CompletableResponseListener(request); + CompletableFuture asyncResponse = completableResponseListener.send(); + assertThatNoException().isThrownBy(() -> { + await() + .atMost(750, TimeUnit.MILLISECONDS) + .until(() -> activeSuspended.getCount() == 1L); + asyncResponse.get(); + }); + assertThat(activeSuspended.getCount()).isEqualTo(0L); + } + + @Test + public void testAsyncDispatches() throws Exception { + servletContextHandler.addFilter((request, response, chain) -> { + if (!(request instanceof HttpServletRequest)) { + throw new IllegalStateException("Expecting ServletRequest to be an instance of HttpServletRequest"); + } + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + if ("/".equals(httpServletRequest.getRequestURI())) { + AsyncContext asyncContext = request.startAsync(); + asyncContext.dispatch("/dispatch"); + return; + } + if ("/dispatch".equals(httpServletRequest.getRequestURI())) { + AsyncContext asyncContext = request.startAsync(); + if (!(response instanceof HttpServletResponse)) { + throw new IllegalStateException("Expecting ServletResponse to be an instance of HttpServletResponse"); + } + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + httpServletResponse.setStatus(204); + asyncContext.complete(); + return; + } + throw new UnsupportedOperationException("Only '/' and '/dispatch' are valid paths"); + }, "/*", EnumSet.allOf(DispatcherType.class)); + + ContentResponse contentResponse = client.GET(uri("/")); + assertThat(contentResponse).isNotNull().extracting(Response::getStatus).isEqualTo(204); + Meter asyncDispatches = registry.meter(MetricRegistry.name(ServletHandler.class, "handler.async-dispatches")); + assertThat(asyncDispatches.getCount()).isEqualTo(1L); + } +} diff --git a/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10HandlerTest.java b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10HandlerTest.java index e2c56ac42a..3426093496 100644 --- a/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10HandlerTest.java +++ b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/InstrumentedEE10HandlerTest.java @@ -2,18 +2,13 @@ import com.codahale.metrics.MetricRegistry; import org.eclipse.jetty.client.ContentResponse; -import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.ee10.servlet.DefaultServlet; -import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.ee10.servlet.ServletContextRequest; import org.eclipse.jetty.ee10.servlet.ServletHandler; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.util.Callback; -import org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -32,39 +27,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -public class InstrumentedEE10HandlerTest { - private final HttpClient client = new HttpClient(); - private final MetricRegistry registry = new MetricRegistry(); - private final Server server = new Server(); - private final ServerConnector connector = new ServerConnector(server); - private final InstrumentedEE10Handler handler = new InstrumentedEE10Handler(registry, null, ALL); +public class InstrumentedEE10HandlerTest extends AbstractIntegrationTest { - @Before - public void setUp() throws Exception { - handler.setName("handler"); - - TestHandler testHandler = new TestHandler(); + @Override + protected Handler getHandler() { + InstrumentedEE10HandlerTest.TestHandler testHandler = new InstrumentedEE10HandlerTest.TestHandler(); // a servlet handler needs a servlet mapping, else the request will be short-circuited // so use the DefaultServlet here testHandler.addServletWithMapping(DefaultServlet.class, "/"); - - // builds the following handler chain: - // ServletContextHandler -> InstrumentedHandler -> TestHandler - // the ServletContextHandler is needed to utilize servlet related classes - ServletContextHandler servletContextHandler = new ServletContextHandler(); - servletContextHandler.setHandler(testHandler); - servletContextHandler.insertHandler(handler); - server.setHandler(servletContextHandler); - - server.addConnector(connector); - server.start(); - client.start(); - } - - @After - public void tearDown() throws Exception { - server.stop(); - client.stop(); + return testHandler; } @Test @@ -183,10 +154,6 @@ private void assertResponseTimesValid() { .getSnapshot().getMedian()).isGreaterThan(0.0).isLessThan(TimeUnit.SECONDS.toNanos(1)); } - private String uri(String path) { - return "http://localhost:" + connector.getLocalPort() + path; - } - private String metricName() { return MetricRegistry.name(TestHandler.class.getName(), "handler"); } diff --git a/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/ResponseStatusTest.java b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/ResponseStatusTest.java new file mode 100644 index 0000000000..3afd0f19e6 --- /dev/null +++ b/metrics-jetty12-ee10/src/test/java/io/dropwizard/metrics/jetty12/ee10/ResponseStatusTest.java @@ -0,0 +1,60 @@ +package io.dropwizard.metrics.jetty12.ee10; + +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.client.StringRequestContent; +import org.eclipse.jetty.ee10.servlet.DefaultServlet; +import org.eclipse.jetty.ee10.servlet.ServletHandler; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ResponseStatusTest extends AbstractIntegrationTest { + + @Override + protected Handler getHandler() { + ServletHandler servletHandler = new ResponseStatusHandler(); + servletHandler.addServletWithMapping(DefaultServlet.class, "/"); + return servletHandler; + } + + @Test + public void testResponseCodes() throws Exception { + + for (int i = 2; i <= 5; i++) { + String status = String.format("%d00", i); + ContentResponse contentResponse = client.POST(uri("/")) + .body(new StringRequestContent(status)) + .headers(headers -> headers.add("Content-Type", "text/plain")) + .send(); + assertThat(contentResponse).isNotNull().satisfies(response -> + assertThat(response.getStatus()).hasToString(status)); + + Meter meter = registry.meter(MetricRegistry.name(ResponseStatusHandler.class, String.format("handler.%dxx-responses", i))); + assertThat(meter.getCount()).isEqualTo(1L); + } + } + + private static class ResponseStatusHandler extends ServletHandler { + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + try (InputStream inputStream = Request.asInputStream(request); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { + String status = bufferedReader.readLine(); + int statusCode = Integer.parseInt(status); + response.setStatus(statusCode); + callback.succeeded(); + return true; + } + } + } +} diff --git a/pom.xml b/pom.xml index 8cd17ce053..42827c6fc6 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,7 @@ 2.25.0 9+181-r4173-1 6.0.0 + 4.2.0 dropwizard_metrics dropwizard