diff --git a/jetty-servlet/pom.xml b/jetty-servlet/pom.xml
index 00abe4bbbb0..2217267cde0 100644
--- a/jetty-servlet/pom.xml
+++ b/jetty-servlet/pom.xml
@@ -41,6 +41,11 @@
jetty-security
${project.version}
+
+ org.eclipse.jetty
+ jetty-util-ajax
+ ${project.version}
+
org.eclipse.jetty
jetty-jmx
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java
index 55fd67963ae..3c6f325dd8b 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java
@@ -19,29 +19,41 @@
package org.eclipse.jetty.servlet;
import java.io.IOException;
-import java.io.PrintWriter;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.QuotedQualityCSV;
import org.eclipse.jetty.io.ConnectionStatistics;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ConnectorStatistics;
-import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
-import org.eclipse.jetty.util.component.Container;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.ajax.JSON;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
public class StatisticsServlet extends HttpServlet
{
private static final Logger LOG = Log.getLogger(StatisticsServlet.class);
@@ -49,7 +61,7 @@ public class StatisticsServlet extends HttpServlet
boolean _restrictToLocalhost = true; // defaults to true
private StatisticsHandler _statsHandler;
private MemoryMXBean _memoryBean;
- private Connector[] _connectors;
+ private List _connectors;
@Override
public void init() throws ServletException
@@ -58,20 +70,16 @@ public class StatisticsServlet extends HttpServlet
ContextHandler.Context scontext = (ContextHandler.Context)context;
Server server = scontext.getContextHandler().getServer();
- Handler handler = server.getChildHandlerByClass(StatisticsHandler.class);
+ _statsHandler = server.getChildHandlerByClass(StatisticsHandler.class);
- if (handler != null)
- {
- _statsHandler = (StatisticsHandler)handler;
- }
- else
+ if (_statsHandler == null)
{
LOG.warn("Statistics Handler not installed!");
return;
}
_memoryBean = ManagementFactory.getMemoryMXBean();
- _connectors = server.getConnectors();
+ _connectors = Arrays.asList(server.getConnectors());
if (getInitParameter("restrictToLocalhost") != null)
{
@@ -80,47 +88,150 @@ public class StatisticsServlet extends HttpServlet
}
@Override
- public void doPost(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException
+ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- doGet(sreq, sres);
+ doGet(request, response);
}
@Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
if (_statsHandler == null)
{
LOG.warn("Statistics Handler not installed!");
- resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+ response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return;
}
if (_restrictToLocalhost)
{
- if (!isLoopbackAddress(req.getRemoteAddr()))
+ if (!isLoopbackAddress(request.getRemoteAddr()))
{
- resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
}
- if (Boolean.parseBoolean(req.getParameter("statsReset")))
+ if (Boolean.parseBoolean(request.getParameter("statsReset")))
{
+ response.setStatus(HttpServletResponse.SC_OK);
_statsHandler.statsReset();
return;
}
- String wantXml = req.getParameter("xml");
- if (wantXml == null)
- wantXml = req.getParameter("XML");
+ List acceptable = getOrderedAcceptableMimeTypes(request);
- if (Boolean.parseBoolean(wantXml))
+ for (String mimeType : acceptable)
{
- sendXmlResponse(resp);
+ switch (mimeType)
+ {
+ case "application/json":
+ writeJsonResponse(response);
+ return;
+ case "text/xml":
+ writeXmlResponse(response);
+ return;
+ case "text/html":
+ writeHtmlResponse(response);
+ return;
+ case "text/plain":
+ writeTextResponse(response);
+ return;
+ case "*/*":
+ String wantXml = request.getParameter("xml");
+ if (wantXml == null)
+ wantXml = request.getParameter("XML");
+
+ if (Boolean.parseBoolean(wantXml))
+ {
+ writeXmlResponse(response);
+ }
+ else
+ {
+ writeTextResponse(response);
+ }
+ return;
+ default:
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("Ignoring unrecognized mime-type {}", mimeType);
+ }
+ break;
+ }
}
- else
+ // None of the listed `Accept` mime-types were found.
+ response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
+ }
+
+ private void writeTextResponse(HttpServletResponse response) throws IOException
+ {
+ response.setCharacterEncoding("utf-8");
+ response.setContentType("text/plain");
+ CharSequence text = generateResponse(new TextProducer());
+ response.getWriter().print(text.toString());
+ }
+
+ private void writeHtmlResponse(HttpServletResponse response) throws IOException
+ {
+ response.setCharacterEncoding("utf-8");
+ response.setContentType("text/html");
+ Writer htmlWriter = new OutputStreamWriter(response.getOutputStream(), UTF_8);
+ htmlWriter.append("");
+ htmlWriter.append(this.getClass().getSimpleName());
+ htmlWriter.append("\n");
+ CharSequence html = generateResponse(new HtmlProducer());
+ htmlWriter.append(html.toString());
+ htmlWriter.append("\n\n");
+ htmlWriter.flush();
+ }
+
+ private void writeXmlResponse(HttpServletResponse response) throws IOException
+ {
+ response.setCharacterEncoding("utf-8");
+ response.setContentType("text/xml");
+ CharSequence xml = generateResponse(new XmlProducer());
+ response.getWriter().print(xml.toString());
+ }
+
+ private void writeJsonResponse(HttpServletResponse response) throws IOException
+ {
+ // We intentionally don't put "UTF-8" into the response headers
+ // as the rules for application/json state that it should never be
+ // present on the HTTP Content-Type header.
+ // It is also true that the application/json mime-type is always UTF-8.
+ response.setContentType("application/json");
+ CharSequence json = generateResponse(new JsonProducer());
+ Writer jsonWriter = new OutputStreamWriter(response.getOutputStream(), UTF_8);
+ jsonWriter.append(json);
+ jsonWriter.flush();
+ }
+
+ private List getOrderedAcceptableMimeTypes(HttpServletRequest request)
+ {
+ QuotedQualityCSV values = null;
+ Enumeration enumAccept = request.getHeaders("Accept");
+ if (enumAccept != null)
{
- sendTextResponse(resp);
+ while (enumAccept.hasMoreElements())
+ {
+ String value = enumAccept.nextElement();
+ if (StringUtil.isNotBlank(value))
+ {
+ if (values == null)
+ {
+ values = new QuotedQualityCSV(QuotedQualityCSV.MOST_SPECIFIC_MIME_ORDERING);
+ }
+ values.addValue(value);
+ }
+ }
}
+
+ if (values != null)
+ {
+ return values.getValues();
+ }
+
+ // No accept specified, return that we allow ALL mime types
+ return Collections.singletonList("*/*");
}
private boolean isLoopbackAddress(String address)
@@ -137,177 +248,362 @@ public class StatisticsServlet extends HttpServlet
}
}
- private void sendXmlResponse(HttpServletResponse response) throws IOException
+ private CharSequence generateResponse(OutputProducer outputProducer)
{
- StringBuilder sb = new StringBuilder();
+ Map top = new HashMap<>();
- sb.append("\n");
+ // requests
+ Map requests = new HashMap<>();
+ requests.put("statsOnMs", _statsHandler.getStatsOnMs());
- sb.append(" \n");
- sb.append(" ").append(_statsHandler.getStatsOnMs()).append("\n");
+ requests.put("requests", _statsHandler.getRequests());
- sb.append(" ").append(_statsHandler.getRequests()).append("\n");
- sb.append(" ").append(_statsHandler.getRequestsActive()).append("\n");
- sb.append(" ").append(_statsHandler.getRequestsActiveMax()).append("\n");
- sb.append(" ").append(_statsHandler.getRequestTimeTotal()).append("\n");
- sb.append(" ").append(_statsHandler.getRequestTimeMean()).append("\n");
- sb.append(" ").append(_statsHandler.getRequestTimeMax()).append("\n");
- sb.append(" ").append(_statsHandler.getRequestTimeStdDev()).append("\n");
+ requests.put("requestsActive", _statsHandler.getRequestsActive());
+ requests.put("requestsActiveMax", _statsHandler.getRequestsActiveMax());
+ requests.put("requestsTimeTotal", _statsHandler.getRequestTimeTotal());
+ requests.put("requestsTimeMean", _statsHandler.getRequestTimeMean());
+ requests.put("requestsTimeMax", _statsHandler.getRequestTimeMax());
+ requests.put("requestsTimeStdDev", _statsHandler.getRequestTimeStdDev());
- sb.append(" ").append(_statsHandler.getDispatched()).append("\n");
- sb.append(" ").append(_statsHandler.getDispatchedActive()).append("\n");
- sb.append(" ").append(_statsHandler.getDispatchedActiveMax()).append("\n");
- sb.append(" ").append(_statsHandler.getDispatchedTimeTotal()).append("\n");
- sb.append(" ").append(_statsHandler.getDispatchedTimeMean()).append("\n");
- sb.append(" ").append(_statsHandler.getDispatchedTimeMax()).append("\n");
- sb.append(" ").append(_statsHandler.getDispatchedTimeStdDev()).append("\n");
+ requests.put("dispatched", _statsHandler.getDispatched());
+ requests.put("dispatchedActive", _statsHandler.getDispatchedActive());
+ requests.put("dispatchedActiveMax", _statsHandler.getDispatchedActiveMax());
+ requests.put("dispatchedTimeTotal", _statsHandler.getDispatchedTimeTotal());
+ requests.put("dispatchedTimeMean", _statsHandler.getDispatchedTimeMean());
+ requests.put("dispatchedTimeMax", _statsHandler.getDispatchedTimeMax());
+ requests.put("dispatchedTimeStdDev", _statsHandler.getDispatchedTimeStdDev());
- sb.append(" ").append(_statsHandler.getAsyncRequests()).append("\n");
- sb.append(" ").append(_statsHandler.getAsyncRequestsWaiting()).append("\n");
- sb.append(" ").append(_statsHandler.getAsyncRequestsWaitingMax()).append("\n");
- sb.append(" ").append(_statsHandler.getAsyncDispatches()).append("\n");
- sb.append(" ").append(_statsHandler.getExpires()).append("\n");
- sb.append(" \n");
+ requests.put("asyncRequests", _statsHandler.getAsyncRequests());
+ requests.put("requestsSuspended", _statsHandler.getAsyncDispatches());
+ requests.put("requestsSuspendedMax", _statsHandler.getAsyncRequestsWaiting());
+ requests.put("requestsResumed", _statsHandler.getAsyncRequestsWaitingMax());
+ requests.put("requestsExpired", _statsHandler.getExpires());
- sb.append(" \n");
- sb.append(" ").append(_statsHandler.getResponses1xx()).append("\n");
- sb.append(" ").append(_statsHandler.getResponses2xx()).append("\n");
- sb.append(" ").append(_statsHandler.getResponses3xx()).append("\n");
- sb.append(" ").append(_statsHandler.getResponses4xx()).append("\n");
- sb.append(" ").append(_statsHandler.getResponses5xx()).append("\n");
- sb.append(" ").append(_statsHandler.getResponsesBytesTotal()).append("\n");
- sb.append(" \n");
+ requests.put("errors", _statsHandler.getErrors());
- sb.append(" \n");
- for (Connector connector : _connectors)
+ top.put("requests", requests);
+
+ // responses
+ Map responses = new HashMap<>();
+ responses.put("responses1xx", _statsHandler.getResponses1xx());
+ responses.put("responses2xx", _statsHandler.getResponses2xx());
+ responses.put("responses3xx", _statsHandler.getResponses3xx());
+ responses.put("responses4xx", _statsHandler.getResponses4xx());
+ responses.put("responses5xx", _statsHandler.getResponses5xx());
+ responses.put("responsesBytesTotal", _statsHandler.getResponsesBytesTotal());
+ top.put("responses", responses);
+
+ // connections
+ List\n");
+ connections.add(connectorDetail);
+ });
+ top.put("connections", connections);
- sb.append(" \n");
- sb.append(" ").append(_memoryBean.getHeapMemoryUsage().getUsed()).append("\n");
- sb.append(" ").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append("\n");
- sb.append(" \n");
+ // memory
+ Map memoryMap = new HashMap<>();
+ memoryMap.put("heapMemoryUsage", _memoryBean.getHeapMemoryUsage().getUsed());
+ memoryMap.put("nonHeapMemoryUsage", _memoryBean.getNonHeapMemoryUsage().getUsed());
+ top.put("memory", memoryMap);
- sb.append("\n");
-
- response.setContentType("text/xml");
- PrintWriter pout = response.getWriter();
- pout.write(sb.toString());
+ // the top level object
+ return outputProducer.generate("statistics", top);
}
- private void sendTextResponse(HttpServletResponse response) throws IOException
+ private interface OutputProducer
{
- StringBuilder sb = new StringBuilder();
- sb.append(_statsHandler.toStatsHTML());
+ CharSequence generate(String id, Map map);
+ }
- sb.append("Connections:
\n");
- for (Connector connector : _connectors)
+ private static class JsonProducer implements OutputProducer
+ {
+ @Override
+ public CharSequence generate(String id, Map map)
{
- sb.append("").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("
");
- sb.append("Protocols:");
- for (String protocol : connector.getProtocols())
- {
- sb.append(protocol).append(" ");
- }
- sb.append("
\n");
+ return JSON.toString(map);
+ }
+ }
- ConnectionStatistics connectionStats = null;
- if (connector instanceof Container)
- connectionStats = connector.getBean(ConnectionStatistics.class);
- if (connectionStats != null)
+ private static class XmlProducer implements OutputProducer
+ {
+ private final StringBuilder sb;
+ private int indent = 0;
+
+ public XmlProducer()
+ {
+ this.sb = new StringBuilder();
+ }
+
+ @Override
+ public CharSequence generate(String id, Map map)
+ {
+ add(id, map);
+ return sb;
+ }
+
+ private void indent()
+ {
+ sb.append("\n");
+ for (int i = 0; i < indent; i++)
{
- sb.append("Total connections: ").append(connectionStats.getConnectionsTotal()).append("
\n");
- sb.append("Current connections open: ").append(connectionStats.getConnections()).append("
\n");
- sb.append("Max concurrent connections open: ").append(connectionStats.getConnectionsMax()).append("
\n");
- sb.append("Mean connection duration: ").append(connectionStats.getConnectionDurationMean()).append("
\n");
- sb.append("Max connection duration: ").append(connectionStats.getConnectionDurationMax()).append("
\n");
- sb.append("Connection duration standard deviation: ").append(connectionStats.getConnectionDurationStdDev()).append("
\n");
- sb.append("Total bytes received: ").append(connectionStats.getReceivedBytes()).append("
\n");
- sb.append("Total bytes sent: ").append(connectionStats.getSentBytes()).append("
\n");
- sb.append("Total messages received: ").append(connectionStats.getReceivedMessages()).append("
\n");
- sb.append("Total messages sent: ").append(connectionStats.getSentMessages()).append("
\n");
- }
- else
- {
- ConnectorStatistics connectorStats = null;
- if (connector instanceof AbstractConnector)
- connectorStats = connector.getBean(ConnectorStatistics.class);
- if (connectorStats != null)
- {
- sb.append("Statistics gathering started ").append(connectorStats.getStartedMillis()).append("ms ago").append("
\n");
- sb.append("Total connections: ").append(connectorStats.getConnections()).append("
\n");
- sb.append("Current connections open: ").append(connectorStats.getConnectionsOpen()).append("
\n");
- sb.append("Max concurrent connections open: ").append(connectorStats.getConnectionsOpenMax()).append("
\n");
- sb.append("Mean connection duration: ").append(connectorStats.getConnectionDurationMean()).append("
\n");
- sb.append("Max connection duration: ").append(connectorStats.getConnectionDurationMax()).append("
\n");
- sb.append("Connection duration standard deviation: ").append(connectorStats.getConnectionDurationStdDev()).append("
\n");
- sb.append("Total messages in: ").append(connectorStats.getMessagesIn()).append("
\n");
- sb.append("Total messages out: ").append(connectorStats.getMessagesOut()).append("
\n");
- }
- else
- {
- sb.append("Statistics gathering off.\n");
- }
+ sb.append(' ').append(' ');
}
}
- sb.append("Memory:
\n");
- sb.append("Heap memory usage: ").append(_memoryBean.getHeapMemoryUsage().getUsed()).append(" bytes").append("
\n");
- sb.append("Non-heap memory usage: ").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append(" bytes").append("
\n");
+ private void add(String id, Object obj)
+ {
+ sb.append('<').append(StringUtil.sanitizeXmlString(id)).append('>');
+ indent++;
- response.setContentType("text/html");
- PrintWriter pout = response.getWriter();
- pout.write(sb.toString());
+ boolean wasIndented = false;
+
+ if (obj instanceof Map)
+ {
+ //noinspection unchecked
+ addMap((Map)obj);
+ wasIndented = true;
+ }
+ else if (obj instanceof List)
+ {
+ addList(id, (List>)obj);
+ wasIndented = true;
+ }
+ else
+ {
+ addObject(obj);
+ }
+
+ indent--;
+ if (wasIndented)
+ indent();
+ sb.append("").append(id).append('>');
+ }
+
+ private void addMap(Map map)
+ {
+ map.keySet().stream().sorted()
+ .forEach((key) ->
+ {
+ indent();
+ add(key, map.get(key));
+ });
+ }
+
+ private void addList(String parentId, List> list)
+ {
+ // drop the 's' at the end.
+ String childName = parentId.replaceFirst("s$", "");
+ list.forEach((entry) ->
+ {
+ indent();
+ add(childName, entry);
+ });
+ }
+
+ private void addObject(Object obj)
+ {
+ sb.append(StringUtil.sanitizeXmlString(Objects.toString(obj)));
+ }
+ }
+
+ private static class TextProducer implements OutputProducer
+ {
+ private final StringBuilder sb;
+ private int indent = 0;
+
+ public TextProducer()
+ {
+ this.sb = new StringBuilder();
+ }
+
+ @Override
+ public CharSequence generate(String id, Map map)
+ {
+ add(id, map);
+ return sb;
+ }
+
+ private void indent()
+ {
+ for (int i = 0; i < indent; i++)
+ {
+ sb.append(' ').append(' ');
+ }
+ }
+
+ private void add(String id, Object obj)
+ {
+ indent();
+ sb.append(id).append(": ");
+ indent++;
+
+ if (obj instanceof Map)
+ {
+ sb.append('\n');
+ //noinspection unchecked
+ addMap((Map)obj);
+ }
+ else if (obj instanceof List)
+ {
+ sb.append('\n');
+ addList(id, (List>)obj);
+ }
+ else
+ {
+ addObject(obj);
+ sb.append('\n');
+ }
+
+ indent--;
+ }
+
+ private void addMap(Map map)
+ {
+ map.keySet().stream().sorted()
+ .forEach((key) -> add(key, map.get(key)));
+ }
+
+ private void addList(String parentId, List> list)
+ {
+ // drop the 's' at the end.
+ String childName = parentId.replaceFirst("s$", "");
+ list.forEach((entry) -> add(childName, entry));
+ }
+
+ private void addObject(Object obj)
+ {
+ sb.append(obj);
+ }
+ }
+
+ private static class HtmlProducer implements OutputProducer
+ {
+ private final StringBuilder sb;
+ private int indent = 0;
+
+ public HtmlProducer()
+ {
+ this.sb = new StringBuilder();
+ }
+
+ @Override
+ public CharSequence generate(String id, Map map)
+ {
+ sb.append("\n");
+ add(id, map);
+ sb.append("
\n");
+ return sb;
+ }
+
+ private void indent()
+ {
+ for (int i = 0; i < indent; i++)
+ {
+ sb.append(' ').append(' ');
+ }
+ }
+
+ private void add(String id, Object obj)
+ {
+ indent();
+ indent++;
+ sb.append("").append(StringUtil.sanitizeXmlString(id)).append(": ");
+ if (obj instanceof Map)
+ {
+ //noinspection unchecked
+ addMap((Map)obj);
+ indent();
+ }
+ else if (obj instanceof List)
+ {
+ addList(id, (List>)obj);
+ indent();
+ }
+ else
+ {
+ addObject(obj);
+ }
+ sb.append("\n");
+
+ indent--;
+ }
+
+ private void addMap(Map map)
+ {
+ sb.append("\n");
+ indent();
+ sb.append("\n");
+ indent++;
+ map.keySet().stream().sorted(String::compareToIgnoreCase)
+ .forEach((key) -> add(key, map.get(key)));
+ indent--;
+ indent();
+ sb.append("
\n");
+ }
+
+ private void addList(String parentId, List> list)
+ {
+ sb.append("\n");
+ indent();
+ sb.append("\n");
+ indent++;
+ // drop the 's' at the end.
+ String childName = parentId.replaceFirst("s$", "");
+ list.forEach((entry) -> add(childName, entry));
+ indent--;
+ indent();
+ sb.append("
\n");
+ }
+
+ private void addObject(Object obj)
+ {
+ sb.append(StringUtil.sanitizeXmlString(Objects.toString(obj)));
+ }
}
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/StatisticsServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/StatisticsServletTest.java
index ef99d909e3b..5590d9bd285 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/StatisticsServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/StatisticsServletTest.java
@@ -18,31 +18,42 @@
package org.eclipse.jetty.servlet;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.nio.ByteBuffer;
+import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
+import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.util.ajax.JSON;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
public class StatisticsServletTest
{
@@ -66,9 +77,7 @@ public class StatisticsServletTest
_server.join();
}
- @Test
- public void getStats()
- throws Exception
+ private void addStatisticsHandler()
{
StatisticsHandler statsHandler = new StatisticsHandler();
_server.setHandler(statsHandler);
@@ -78,30 +87,196 @@ public class StatisticsServletTest
servletHolder.setInitParameter("restrictToLocalhost", "false");
statsContext.addServlet(servletHolder, "/stats");
statsContext.setSessionHandler(new SessionHandler());
+ }
+
+ @Test
+ public void getStats()
+ throws Exception
+ {
+ addStatisticsHandler();
_server.start();
- getResponse("/test1");
- String response = getResponse("/stats?xml=true");
- Stats stats = parseStats(response);
+ HttpTester.Response response;
+
+ // Trigger 2xx response
+ response = getResponse("/test1");
+ assertEquals(response.getStatus(), 200);
+
+ // Look for 200 response that was tracked
+ response = getResponse("/stats?xml=true");
+ assertEquals(response.getStatus(), 200);
+ Stats stats = parseStats(response.getContent());
assertEquals(1, stats.responses2xx);
- getResponse("/stats?statsReset=true");
+ // Reset stats
+ response = getResponse("/stats?statsReset=true");
+ assertEquals(response.getStatus(), 200);
+
+ // Request stats again
response = getResponse("/stats?xml=true");
- stats = parseStats(response);
+ assertEquals(response.getStatus(), 200);
+ stats = parseStats(response.getContent());
assertEquals(1, stats.responses2xx);
- getResponse("/test1");
- getResponse("/nothing");
- response = getResponse("/stats?xml=true");
- stats = parseStats(response);
+ // Trigger 2xx response
+ response = getResponse("/test1");
+ assertEquals(response.getStatus(), 200);
+ // Trigger 4xx response
+ response = getResponse("/nothing");
+ assertEquals(response.getStatus(), 404);
+ // Request stats again
+ response = getResponse("/stats?xml=true");
+ assertEquals(response.getStatus(), 200);
+ stats = parseStats(response.getContent());
+
+ // Verify we see (from last reset)
+ // 1) request for /stats?statsReset=true [2xx]
+ // 2) request for /stats?xml=true [2xx]
+ // 3) request for /test1 [2xx]
+ // 4) request for /nothing [4xx]
assertThat("2XX Response Count" + response, stats.responses2xx, is(3));
assertThat("4XX Response Count" + response, stats.responses4xx, is(1));
}
- public String getResponse(String path)
+ @Test
+ public void getXmlResponse()
+ throws Exception
+ {
+ addStatisticsHandler();
+ _server.start();
+
+ HttpTester.Response response;
+ HttpTester.Request request = new HttpTester.Request();
+
+ request.setMethod("GET");
+ request.setURI("/stats");
+ request.setHeader("Accept", "text/xml");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "test");
+
+ ByteBuffer responseBuffer = _connector.getResponse(request.generate());
+ response = HttpTester.parseResponse(responseBuffer);
+
+ assertThat("Response.contentType", response.get(HttpHeader.CONTENT_TYPE), containsString("text/xml"));
+
+ // System.out.println(response.getContent());
+
+ // Parse it, make sure it's well formed.
+ DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
+ try (ByteArrayInputStream input = new ByteArrayInputStream(response.getContentBytes()))
+ {
+ Document doc = docBuilder.parse(input);
+ assertNotNull(doc);
+ assertEquals("statistics", doc.getDocumentElement().getNodeName());
+ }
+ }
+
+ @Test
+ public void getJsonResponse()
+ throws Exception
+ {
+ addStatisticsHandler();
+ _server.start();
+
+ HttpTester.Response response;
+ HttpTester.Request request = new HttpTester.Request();
+
+ request.setMethod("GET");
+ request.setURI("/stats");
+ request.setHeader("Accept", "application/json");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "test");
+
+ ByteBuffer responseBuffer = _connector.getResponse(request.generate());
+ response = HttpTester.parseResponse(responseBuffer);
+
+ assertThat("Response.contentType", response.get(HttpHeader.CONTENT_TYPE), is("application/json"));
+ assertThat("Response.contentType for json should never contain a charset",
+ response.get(HttpHeader.CONTENT_TYPE), not(containsString("charset")));
+
+ // System.out.println(response.getContent());
+
+ // Parse it, make sure it's well formed.
+ Object doc = JSON.parse(response.getContent());
+ assertNotNull(doc);
+ assertThat(doc, instanceOf(Map.class));
+ Map, ?> docMap = (Map, ?>)doc;
+ assertEquals(4, docMap.size());
+ assertNotNull(docMap.get("requests"));
+ assertNotNull(docMap.get("responses"));
+ assertNotNull(docMap.get("connections"));
+ assertNotNull(docMap.get("memory"));
+ }
+
+ @Test
+ public void getTextResponse()
+ throws Exception
+ {
+ addStatisticsHandler();
+ _server.start();
+
+ HttpTester.Response response;
+ HttpTester.Request request = new HttpTester.Request();
+
+ request.setMethod("GET");
+ request.setURI("/stats");
+ request.setHeader("Accept", "text/plain");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "test");
+
+ ByteBuffer responseBuffer = _connector.getResponse(request.generate());
+ response = HttpTester.parseResponse(responseBuffer);
+
+ assertThat("Response.contentType", response.get(HttpHeader.CONTENT_TYPE), containsString("text/plain"));
+
+ // System.out.println(response.getContent());
+
+ // Look for expected content
+ assertThat(response.getContent(), containsString("requests: "));
+ assertThat(response.getContent(), containsString("responses: "));
+ assertThat(response.getContent(), containsString("connections: "));
+ assertThat(response.getContent(), containsString("memory: "));
+ }
+
+ @Test
+ public void getHtmlResponse()
+ throws Exception
+ {
+ addStatisticsHandler();
+ _server.start();
+
+ HttpTester.Response response;
+ HttpTester.Request request = new HttpTester.Request();
+
+ request.setMethod("GET");
+ request.setURI("/stats");
+ request.setHeader("Accept", "text/html");
+ request.setVersion(HttpVersion.HTTP_1_1);
+ request.setHeader("Host", "test");
+
+ ByteBuffer responseBuffer = _connector.getResponse(request.generate());
+ response = HttpTester.parseResponse(responseBuffer);
+
+ assertThat("Response.contentType", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html"));
+
+ System.out.println(response.getContent());
+
+ // Look for things that indicate it's a well formed HTML output
+ assertThat(response.getContent(), containsString(""));
+ assertThat(response.getContent(), containsString(""));
+ assertThat(response.getContent(), containsString("requests: "));
+ assertThat(response.getContent(), containsString("responses: "));
+ assertThat(response.getContent(), containsString("connections: "));
+ assertThat(response.getContent(), containsString("memory: "));
+ assertThat(response.getContent(), containsString(""));
+ assertThat(response.getContent(), containsString(""));
+ }
+
+ public HttpTester.Response getResponse(String path)
throws Exception
{
HttpTester.Request request = new HttpTester.Request();
@@ -111,7 +286,7 @@ public class StatisticsServletTest
request.setHeader("Host", "test");
ByteBuffer responseBuffer = _connector.getResponse(request.generate());
- return HttpTester.parseResponse(responseBuffer).getContent();
+ return HttpTester.parseResponse(responseBuffer);
}
public Stats parseStats(String xml)
@@ -120,7 +295,6 @@ public class StatisticsServletTest
XPath xPath = XPathFactory.newInstance().newXPath();
String responses4xx = xPath.evaluate("//responses4xx", new InputSource(new StringReader(xml)));
-
String responses2xx = xPath.evaluate("//responses2xx", new InputSource(new StringReader(xml)));
return new Stats(Integer.parseInt(responses2xx), Integer.parseInt(responses4xx));