Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x
This commit is contained in:
commit
d30d95c26c
|
@ -177,6 +177,7 @@ public class TestOSGiUtil
|
|||
.start());
|
||||
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-slf4j-impl").versionAsInProject().start());
|
||||
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-util").versionAsInProject().start());
|
||||
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-util-ajax").versionAsInProject().start());
|
||||
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-deploy").versionAsInProject().start());
|
||||
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-server").versionAsInProject().start());
|
||||
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-servlet").versionAsInProject().start());
|
||||
|
|
|
@ -8,6 +8,10 @@ server
|
|||
|
||||
[depend]
|
||||
server
|
||||
servlet
|
||||
|
||||
[lib]
|
||||
lib/jetty-util-ajax-${jetty.version}.jar
|
||||
|
||||
[xml]
|
||||
etc/jetty-stats.xml
|
||||
|
|
|
@ -17,6 +17,15 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<argLine>
|
||||
@{argLine} ${jetty.surefire.argLine}
|
||||
--add-modules org.eclipse.jetty.util.ajax
|
||||
</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
@ -49,6 +58,12 @@
|
|||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util-ajax</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-jmx</artifactId>
|
||||
|
|
|
@ -27,6 +27,7 @@ module org.eclipse.jetty.servlet
|
|||
|
||||
// Only required if using StatisticsServlet.
|
||||
requires static java.management;
|
||||
requires static org.eclipse.jetty.util.ajax;
|
||||
// Only required if using IntrospectorCleaner.
|
||||
requires static java.desktop;
|
||||
// Only required if using JMX.
|
||||
|
|
|
@ -19,28 +19,53 @@
|
|||
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 jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
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.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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Collect and report statistics about requests / responses / connections and more.
|
||||
* <p>
|
||||
* You can use normal HTTP content negotiation to ask for the statistics.
|
||||
* Specify a request <code>Accept</code> header for one of the following formats:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li><code>application/json</code></li>
|
||||
* <li><code>text/xml</code></li>
|
||||
* <li><code>text/html</code></li>
|
||||
* <li><code>text/plain</code> - default if no <code>Accept</code> header specified</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class StatisticsServlet extends HttpServlet
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StatisticsServlet.class);
|
||||
|
@ -48,7 +73,7 @@ public class StatisticsServlet extends HttpServlet
|
|||
boolean _restrictToLocalhost = true; // defaults to true
|
||||
private StatisticsHandler _statsHandler;
|
||||
private MemoryMXBean _memoryBean;
|
||||
private Connector[] _connectors;
|
||||
private List<Connector> _connectors;
|
||||
|
||||
@Override
|
||||
public void init() throws ServletException
|
||||
|
@ -57,20 +82,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)
|
||||
{
|
||||
|
@ -79,47 +100,147 @@ 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");
|
||||
if (request.getParameter("xml") != null)
|
||||
{
|
||||
LOG.warn("'xml' parameter is deprecated, use 'Accept' request header instead");
|
||||
}
|
||||
|
||||
if (Boolean.parseBoolean(wantXml))
|
||||
List<String> acceptable = getOrderedAcceptableMimeTypes(request);
|
||||
|
||||
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":
|
||||
case "*/*":
|
||||
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("<html><head><title>");
|
||||
htmlWriter.append(this.getClass().getSimpleName());
|
||||
htmlWriter.append("</title></head><body>\n");
|
||||
CharSequence html = generateResponse(new HtmlProducer());
|
||||
htmlWriter.append(html.toString());
|
||||
htmlWriter.append("\n</body></html>\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<String> getOrderedAcceptableMimeTypes(HttpServletRequest request)
|
||||
{
|
||||
QuotedQualityCSV values = new QuotedQualityCSV(QuotedQualityCSV.MOST_SPECIFIC_MIME_ORDERING);
|
||||
|
||||
// No accept header specified, try 'accept' parameter (for those clients that are
|
||||
// so ancient that they cannot set the standard HTTP `Accept` header)
|
||||
String acceptParameter = request.getParameter("accept");
|
||||
if (acceptParameter != null)
|
||||
{
|
||||
sendTextResponse(resp);
|
||||
values.addValue(acceptParameter);
|
||||
}
|
||||
|
||||
Enumeration<String> enumAccept = request.getHeaders(HttpHeader.ACCEPT.toString());
|
||||
if (enumAccept != null)
|
||||
{
|
||||
while (enumAccept.hasMoreElements())
|
||||
{
|
||||
String value = enumAccept.nextElement();
|
||||
if (StringUtil.isNotBlank(value))
|
||||
{
|
||||
values.addValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (values.isEmpty())
|
||||
{
|
||||
// return that we allow ALL mime types
|
||||
return Collections.singletonList("*/*");
|
||||
}
|
||||
|
||||
return values.getValues();
|
||||
}
|
||||
|
||||
private boolean isLoopbackAddress(String address)
|
||||
|
@ -136,140 +257,336 @@ public class StatisticsServlet extends HttpServlet
|
|||
}
|
||||
}
|
||||
|
||||
private void sendXmlResponse(HttpServletResponse response) throws IOException
|
||||
private CharSequence generateResponse(OutputProducer outputProducer)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Map<String, Object> top = new HashMap<>();
|
||||
|
||||
sb.append("<statistics>\n");
|
||||
// requests
|
||||
Map<String, Number> requests = new HashMap<>();
|
||||
requests.put("statsOnMs", _statsHandler.getStatsOnMs());
|
||||
|
||||
sb.append(" <requests>\n");
|
||||
sb.append(" <statsOnMs>").append(_statsHandler.getStatsOnMs()).append("</statsOnMs>\n");
|
||||
requests.put("requests", _statsHandler.getRequests());
|
||||
|
||||
sb.append(" <requests>").append(_statsHandler.getRequests()).append("</requests>\n");
|
||||
sb.append(" <requestsActive>").append(_statsHandler.getRequestsActive()).append("</requestsActive>\n");
|
||||
sb.append(" <requestsActiveMax>").append(_statsHandler.getRequestsActiveMax()).append("</requestsActiveMax>\n");
|
||||
sb.append(" <requestsTimeTotal>").append(_statsHandler.getRequestTimeTotal()).append("</requestsTimeTotal>\n");
|
||||
sb.append(" <requestsTimeMean>").append(_statsHandler.getRequestTimeMean()).append("</requestsTimeMean>\n");
|
||||
sb.append(" <requestsTimeMax>").append(_statsHandler.getRequestTimeMax()).append("</requestsTimeMax>\n");
|
||||
sb.append(" <requestsTimeStdDev>").append(_statsHandler.getRequestTimeStdDev()).append("</requestsTimeStdDev>\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(" <dispatched>").append(_statsHandler.getDispatched()).append("</dispatched>\n");
|
||||
sb.append(" <dispatchedActive>").append(_statsHandler.getDispatchedActive()).append("</dispatchedActive>\n");
|
||||
sb.append(" <dispatchedActiveMax>").append(_statsHandler.getDispatchedActiveMax()).append("</dispatchedActiveMax>\n");
|
||||
sb.append(" <dispatchedTimeTotalMs>").append(_statsHandler.getDispatchedTimeTotal()).append("</dispatchedTimeTotalMs>\n");
|
||||
sb.append(" <dispatchedTimeMeanMs>").append(_statsHandler.getDispatchedTimeMean()).append("</dispatchedTimeMeanMs>\n");
|
||||
sb.append(" <dispatchedTimeMaxMs>").append(_statsHandler.getDispatchedTimeMax()).append("</dispatchedTimeMaxMs>\n");
|
||||
sb.append(" <dispatchedTimeStdDevMs>").append(_statsHandler.getDispatchedTimeStdDev()).append("</dispatchedTimeStdDevMs>\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(" <asyncRequests>").append(_statsHandler.getAsyncRequests()).append("</asyncRequests>\n");
|
||||
sb.append(" <requestsSuspended>").append(_statsHandler.getAsyncRequestsWaiting()).append("</requestsSuspended>\n");
|
||||
sb.append(" <requestsSuspendedMax>").append(_statsHandler.getAsyncRequestsWaitingMax()).append("</requestsSuspendedMax>\n");
|
||||
sb.append(" <requestsResumed>").append(_statsHandler.getAsyncDispatches()).append("</requestsResumed>\n");
|
||||
sb.append(" <requestsExpired>").append(_statsHandler.getExpires()).append("</requestsExpired>\n");
|
||||
sb.append(" </requests>\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(" <responses>\n");
|
||||
sb.append(" <responses1xx>").append(_statsHandler.getResponses1xx()).append("</responses1xx>\n");
|
||||
sb.append(" <responses2xx>").append(_statsHandler.getResponses2xx()).append("</responses2xx>\n");
|
||||
sb.append(" <responses3xx>").append(_statsHandler.getResponses3xx()).append("</responses3xx>\n");
|
||||
sb.append(" <responses4xx>").append(_statsHandler.getResponses4xx()).append("</responses4xx>\n");
|
||||
sb.append(" <responses5xx>").append(_statsHandler.getResponses5xx()).append("</responses5xx>\n");
|
||||
sb.append(" <responsesBytesTotal>").append(_statsHandler.getResponsesBytesTotal()).append("</responsesBytesTotal>\n");
|
||||
sb.append(" </responses>\n");
|
||||
requests.put("errors", _statsHandler.getErrors());
|
||||
|
||||
sb.append(" <connections>\n");
|
||||
for (Connector connector : _connectors)
|
||||
top.put("requests", requests);
|
||||
|
||||
// responses
|
||||
Map<String, Number> 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<Object> connections = new ArrayList<>();
|
||||
_connectors.forEach((connector) ->
|
||||
{
|
||||
sb.append(" <connector>\n");
|
||||
sb.append(" <name>").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("</name>\n");
|
||||
sb.append(" <protocols>\n");
|
||||
for (String protocol : connector.getProtocols())
|
||||
{
|
||||
sb.append(" <protocol>").append(protocol).append("</protocol>\n");
|
||||
}
|
||||
sb.append(" </protocols>\n");
|
||||
Map<String, Object> connectorDetail = new HashMap<>();
|
||||
connectorDetail.put("name", String.format("%s@%X", connector.getClass().getName(), connector.hashCode()));
|
||||
connectorDetail.put("protocols", connector.getProtocols());
|
||||
|
||||
ConnectionStatistics connectionStats = null;
|
||||
if (connector instanceof AbstractConnector)
|
||||
connectionStats = ((AbstractConnector)connector).getBean(ConnectionStatistics.class);
|
||||
ConnectionStatistics connectionStats = connector.getBean(ConnectionStatistics.class);
|
||||
if (connectionStats != null)
|
||||
{
|
||||
sb.append(" <statsOn>true</statsOn>\n");
|
||||
sb.append(" <connections>").append(connectionStats.getConnectionsTotal()).append("</connections>\n");
|
||||
sb.append(" <connectionsOpen>").append(connectionStats.getConnections()).append("</connectionsOpen>\n");
|
||||
sb.append(" <connectionsOpenMax>").append(connectionStats.getConnectionsMax()).append("</connectionsOpenMax>\n");
|
||||
sb.append(" <connectionsDurationMean>").append(connectionStats.getConnectionDurationMean()).append("</connectionsDurationMean>\n");
|
||||
sb.append(" <connectionsDurationMax>").append(connectionStats.getConnectionDurationMax()).append("</connectionsDurationMax>\n");
|
||||
sb.append(" <connectionsDurationStdDev>").append(connectionStats.getConnectionDurationStdDev()).append("</connectionsDurationStdDev>\n");
|
||||
sb.append(" <bytesIn>").append(connectionStats.getReceivedBytes()).append("</bytesIn>\n");
|
||||
sb.append(" <bytesOut>").append(connectionStats.getSentBytes()).append("</bytesOut>\n");
|
||||
sb.append(" <messagesIn>").append(connectionStats.getReceivedMessages()).append("</messagesIn>\n");
|
||||
sb.append(" <messagesOut>").append(connectionStats.getSentMessages()).append("</messagesOut>\n");
|
||||
connectorDetail.put("statsOn", true);
|
||||
connectorDetail.put("connections", connectionStats.getConnectionsTotal());
|
||||
connectorDetail.put("connectionsOpen", connectionStats.getConnections());
|
||||
connectorDetail.put("connectionsOpenMax", connectionStats.getConnectionsMax());
|
||||
connectorDetail.put("connectionsDurationMean", connectionStats.getConnectionDurationMean());
|
||||
connectorDetail.put("connectionsDurationMax", connectionStats.getConnectionDurationMax());
|
||||
connectorDetail.put("connectionsDurationStdDev", connectionStats.getConnectionDurationStdDev());
|
||||
connectorDetail.put("bytesIn", connectionStats.getReceivedBytes());
|
||||
connectorDetail.put("bytesOut", connectionStats.getSentBytes());
|
||||
connectorDetail.put("messagesIn", connectionStats.getReceivedMessages());
|
||||
connectorDetail.put("messagesOut", connectionStats.getSentMessages());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" <statsOn>false</statsOn>\n");
|
||||
}
|
||||
sb.append(" </connector>\n");
|
||||
}
|
||||
sb.append(" </connections>\n");
|
||||
connections.add(connectorDetail);
|
||||
});
|
||||
top.put("connections", connections);
|
||||
|
||||
sb.append(" <memory>\n");
|
||||
sb.append(" <heapMemoryUsage>").append(_memoryBean.getHeapMemoryUsage().getUsed()).append("</heapMemoryUsage>\n");
|
||||
sb.append(" <nonHeapMemoryUsage>").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append("</nonHeapMemoryUsage>\n");
|
||||
sb.append(" </memory>\n");
|
||||
// memory
|
||||
Map<String, Number> memoryMap = new HashMap<>();
|
||||
memoryMap.put("heapMemoryUsage", _memoryBean.getHeapMemoryUsage().getUsed());
|
||||
memoryMap.put("nonHeapMemoryUsage", _memoryBean.getNonHeapMemoryUsage().getUsed());
|
||||
top.put("memory", memoryMap);
|
||||
|
||||
sb.append("</statistics>\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<String, Object> map);
|
||||
}
|
||||
|
||||
sb.append("<h2>Connections:</h2>\n");
|
||||
for (Connector connector : _connectors)
|
||||
private static class JsonProducer implements OutputProducer
|
||||
{
|
||||
@Override
|
||||
public CharSequence generate(String id, Map<String, Object> map)
|
||||
{
|
||||
sb.append("<h3>").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("</h3>");
|
||||
sb.append("Protocols:");
|
||||
for (String protocol : connector.getProtocols())
|
||||
{
|
||||
sb.append(protocol).append(" ");
|
||||
}
|
||||
sb.append(" <br />\n");
|
||||
return new JSON().toJSON(map);
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionStatistics connectionStats = null;
|
||||
if (connector instanceof Container)
|
||||
connectionStats = ((Container)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<String, Object> 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("<br />\n");
|
||||
sb.append("Current connections open: ").append(connectionStats.getConnections()).append("<br />\n");
|
||||
sb.append("Max concurrent connections open: ").append(connectionStats.getConnectionsMax()).append("<br />\n");
|
||||
sb.append("Mean connection duration: ").append(connectionStats.getConnectionDurationMean()).append("<br />\n");
|
||||
sb.append("Max connection duration: ").append(connectionStats.getConnectionDurationMax()).append("<br />\n");
|
||||
sb.append("Connection duration standard deviation: ").append(connectionStats.getConnectionDurationStdDev()).append("<br />\n");
|
||||
sb.append("Total bytes received: ").append(connectionStats.getReceivedBytes()).append("<br />\n");
|
||||
sb.append("Total bytes sent: ").append(connectionStats.getSentBytes()).append("<br />\n");
|
||||
sb.append("Total messages received: ").append(connectionStats.getReceivedMessages()).append("<br />\n");
|
||||
sb.append("Total messages sent: ").append(connectionStats.getSentMessages()).append("<br />\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("Statistics gathering off.\n");
|
||||
sb.append(' ').append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("<h2>Memory:</h2>\n");
|
||||
sb.append("Heap memory usage: ").append(_memoryBean.getHeapMemoryUsage().getUsed()).append(" bytes").append("<br />\n");
|
||||
sb.append("Non-heap memory usage: ").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append(" bytes").append("<br />\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<String, ?>)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<String, ?> 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<String, Object> 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<String, ?>)obj);
|
||||
}
|
||||
else if (obj instanceof List)
|
||||
{
|
||||
sb.append('\n');
|
||||
addList(id, (List<?>)obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
addObject(obj);
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
indent--;
|
||||
}
|
||||
|
||||
private void addMap(Map<String, ?> 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<String, Object> map)
|
||||
{
|
||||
sb.append("<ul>\n");
|
||||
add(id, map);
|
||||
sb.append("</ul>\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("<li><em>").append(StringUtil.sanitizeXmlString(id)).append("</em>: ");
|
||||
if (obj instanceof Map)
|
||||
{
|
||||
//noinspection unchecked
|
||||
addMap((Map<String, ?>)obj);
|
||||
indent();
|
||||
}
|
||||
else if (obj instanceof List)
|
||||
{
|
||||
addList(id, (List<?>)obj);
|
||||
indent();
|
||||
}
|
||||
else
|
||||
{
|
||||
addObject(obj);
|
||||
}
|
||||
sb.append("</li>\n");
|
||||
|
||||
indent--;
|
||||
}
|
||||
|
||||
private void addMap(Map<String, ?> map)
|
||||
{
|
||||
sb.append("\n");
|
||||
indent();
|
||||
sb.append("<ul>\n");
|
||||
indent++;
|
||||
map.keySet().stream().sorted(String::compareToIgnoreCase)
|
||||
.forEach((key) -> add(key, map.get(key)));
|
||||
indent--;
|
||||
indent();
|
||||
sb.append("</ul>\n");
|
||||
}
|
||||
|
||||
private void addList(String parentId, List<?> list)
|
||||
{
|
||||
sb.append("\n");
|
||||
indent();
|
||||
sb.append("<ul>\n");
|
||||
indent++;
|
||||
// drop the 's' at the end.
|
||||
String childName = parentId.replaceFirst("s$", "");
|
||||
list.forEach((entry) -> add(childName, entry));
|
||||
indent--;
|
||||
indent();
|
||||
sb.append("</ul>\n");
|
||||
}
|
||||
|
||||
private void addObject(Object obj)
|
||||
{
|
||||
sb.append(StringUtil.sanitizeXmlString(Objects.toString(obj)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,16 @@
|
|||
|
||||
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 java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
|
@ -29,20 +35,30 @@ import jakarta.servlet.ServletException;
|
|||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
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.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
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 +82,7 @@ public class StatisticsServletTest
|
|||
_server.join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getStats()
|
||||
throws Exception
|
||||
private void addStatisticsHandler()
|
||||
{
|
||||
StatisticsHandler statsHandler = new StatisticsHandler();
|
||||
_server.setHandler(statsHandler);
|
||||
|
@ -78,40 +92,267 @@ public class StatisticsServletTest
|
|||
servletHolder.setInitParameter("restrictToLocalhost", "false");
|
||||
statsContext.addServlet(servletHolder, "/stats");
|
||||
statsContext.setSessionHandler(new SessionHandler());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStats()
|
||||
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");
|
||||
assertEquals(response.getStatus(), 200);
|
||||
Stats stats = parseStats(response.getContent());
|
||||
|
||||
assertEquals(1, stats.responses2xx);
|
||||
|
||||
getResponse("/stats?statsReset=true");
|
||||
response = getResponse("/stats?xml=true");
|
||||
stats = parseStats(response);
|
||||
// Reset stats
|
||||
response = getResponse("/stats?statsReset=true");
|
||||
assertEquals(response.getStatus(), 200);
|
||||
|
||||
// Request stats again
|
||||
response = getResponse("/stats");
|
||||
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");
|
||||
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)
|
||||
public static Stream<Arguments> typeVariations(String mimeType)
|
||||
{
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
new Consumer<HttpTester.Request>()
|
||||
{
|
||||
@Override
|
||||
public void accept(HttpTester.Request request)
|
||||
{
|
||||
request.setURI("/stats");
|
||||
request.setHeader("Accept", mimeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Header[Accept: " + mimeType + "]";
|
||||
}
|
||||
}
|
||||
),
|
||||
Arguments.of(
|
||||
new Consumer<HttpTester.Request>()
|
||||
{
|
||||
@Override
|
||||
public void accept(HttpTester.Request request)
|
||||
{
|
||||
request.setURI("/stats?accept=" + mimeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "query[accept=" + mimeType + "]";
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static Stream<Arguments> xmlVariations()
|
||||
{
|
||||
return typeVariations("text/xml");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}] {0}")
|
||||
@MethodSource("xmlVariations")
|
||||
public void testGetXmlResponse(Consumer<HttpTester.Request> requestCustomizer)
|
||||
throws Exception
|
||||
{
|
||||
addStatisticsHandler();
|
||||
_server.start();
|
||||
|
||||
HttpTester.Response response;
|
||||
HttpTester.Request request = new HttpTester.Request();
|
||||
|
||||
request.setMethod("GET");
|
||||
request.setVersion(HttpVersion.HTTP_1_1);
|
||||
request.setHeader("Host", "test");
|
||||
requestCustomizer.accept(request);
|
||||
|
||||
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();
|
||||
docBuilderFactory.setValidating(false);
|
||||
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
|
||||
try (ByteArrayInputStream input = new ByteArrayInputStream(response.getContentBytes()))
|
||||
{
|
||||
Document doc = docBuilder.parse(input);
|
||||
assertNotNull(doc);
|
||||
assertEquals("statistics", doc.getDocumentElement().getNodeName());
|
||||
}
|
||||
}
|
||||
|
||||
public static Stream<Arguments> jsonVariations()
|
||||
{
|
||||
return typeVariations("application/json");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}] {0}")
|
||||
@MethodSource("jsonVariations")
|
||||
public void testGetJsonResponse(Consumer<HttpTester.Request> requestCustomizer)
|
||||
throws Exception
|
||||
{
|
||||
addStatisticsHandler();
|
||||
_server.start();
|
||||
|
||||
HttpTester.Response response;
|
||||
HttpTester.Request request = new HttpTester.Request();
|
||||
|
||||
request.setMethod("GET");
|
||||
requestCustomizer.accept(request);
|
||||
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 = new JSON().parse(new JSON.StringSource(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"));
|
||||
}
|
||||
|
||||
public static Stream<Arguments> plaintextVariations()
|
||||
{
|
||||
return typeVariations("text/plain");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}] {0}")
|
||||
@MethodSource("plaintextVariations")
|
||||
public void testGetTextResponse(Consumer<HttpTester.Request> requestCustomizer)
|
||||
throws Exception
|
||||
{
|
||||
addStatisticsHandler();
|
||||
_server.start();
|
||||
|
||||
HttpTester.Response response;
|
||||
HttpTester.Request request = new HttpTester.Request();
|
||||
|
||||
request.setMethod("GET");
|
||||
requestCustomizer.accept(request);
|
||||
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: "));
|
||||
}
|
||||
|
||||
public static Stream<Arguments> htmlVariations()
|
||||
{
|
||||
return typeVariations("text/html");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}] {0}")
|
||||
@MethodSource("htmlVariations")
|
||||
public void testGetHtmlResponse(Consumer<HttpTester.Request> requestCustomizer)
|
||||
throws Exception
|
||||
{
|
||||
addStatisticsHandler();
|
||||
_server.start();
|
||||
|
||||
HttpTester.Response response;
|
||||
HttpTester.Request request = new HttpTester.Request();
|
||||
|
||||
request.setMethod("GET");
|
||||
requestCustomizer.accept(request);
|
||||
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("<html>"));
|
||||
assertThat(response.getContent(), containsString("<body>"));
|
||||
assertThat(response.getContent(), containsString("<em>requests</em>: "));
|
||||
assertThat(response.getContent(), containsString("<em>responses</em>: "));
|
||||
assertThat(response.getContent(), containsString("<em>connections</em>: "));
|
||||
assertThat(response.getContent(), containsString("<em>memory</em>: "));
|
||||
assertThat(response.getContent(), containsString("</body>"));
|
||||
assertThat(response.getContent(), containsString("</html>"));
|
||||
}
|
||||
|
||||
public HttpTester.Response getResponse(String path)
|
||||
throws Exception
|
||||
{
|
||||
HttpTester.Request request = new HttpTester.Request();
|
||||
request.setMethod("GET");
|
||||
request.setHeader("Accept", "text/xml");
|
||||
request.setURI(path);
|
||||
request.setVersion(HttpVersion.HTTP_1_1);
|
||||
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 +361,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));
|
||||
|
|
|
@ -134,6 +134,12 @@
|
|||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util-ajax</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-jetty-api</artifactId>
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.tests.distribution;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.util.ajax.JSON;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class StatsTests extends AbstractJettyHomeTest
|
||||
{
|
||||
@Test
|
||||
public void testStatsServlet() throws Exception
|
||||
{
|
||||
String jettyVersion = System.getProperty("jettyVersion");
|
||||
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
|
||||
.jettyVersion(jettyVersion)
|
||||
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
|
||||
.build();
|
||||
|
||||
String[] args1 = {
|
||||
"--create-startd",
|
||||
"--approve-all-licenses",
|
||||
"--add-to-start=resources,server,http,webapp,deploy,stats"
|
||||
};
|
||||
try (JettyHomeTester.Run run1 = distribution.start(args1))
|
||||
{
|
||||
assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
|
||||
assertEquals(0, run1.getExitValue());
|
||||
|
||||
Path webappsDir = distribution.getJettyBase().resolve("webapps");
|
||||
FS.ensureDirExists(webappsDir.resolve("demo"));
|
||||
FS.ensureDirExists(webappsDir.resolve("demo/WEB-INF"));
|
||||
|
||||
distribution.installBaseResource("stats-webapp/index.html", "webapps/demo/index.html");
|
||||
distribution.installBaseResource("stats-webapp/WEB-INF/web.xml", "webapps/demo/WEB-INF/web.xml");
|
||||
|
||||
int port = distribution.freePort();
|
||||
String[] args2 = {
|
||||
"jetty.http.port=" + port
|
||||
};
|
||||
try (JettyHomeTester.Run run2 = distribution.start(args2))
|
||||
{
|
||||
assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
|
||||
|
||||
startHttpClient();
|
||||
|
||||
ContentResponse response;
|
||||
URI serverBaseURI = URI.create("http://localhost:" + port);
|
||||
|
||||
response = client.GET(serverBaseURI.resolve("/demo/index.html"));
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertThat(response.getContentAsString(), containsString("<h1>Stats Demo</h1>"));
|
||||
|
||||
// ---------------
|
||||
// Test XML accept
|
||||
response = client.newRequest(serverBaseURI.resolve("/demo/stats"))
|
||||
.method(HttpMethod.GET)
|
||||
.headers((headers) -> headers.add(HttpHeader.ACCEPT, "text/xml"))
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
|
||||
assertThat("Response.contentType", response.getHeaders().get(HttpHeader.CONTENT_TYPE), containsString("text/xml"));
|
||||
|
||||
// Parse it, make sure it's well formed.
|
||||
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
docBuilderFactory.setValidating(false);
|
||||
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
|
||||
try (ByteArrayInputStream input = new ByteArrayInputStream(response.getContent()))
|
||||
{
|
||||
Document doc = docBuilder.parse(input);
|
||||
assertNotNull(doc);
|
||||
assertEquals("statistics", doc.getDocumentElement().getNodeName());
|
||||
}
|
||||
|
||||
// ---------------
|
||||
// Test JSON accept
|
||||
response = client.newRequest(serverBaseURI.resolve("/demo/stats"))
|
||||
.method(HttpMethod.GET)
|
||||
.headers((headers) -> headers.add(HttpHeader.ACCEPT, "application/json"))
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
|
||||
assertThat("Response.contentType", response.getHeaders().get(HttpHeader.CONTENT_TYPE), containsString("application/json"));
|
||||
|
||||
Object doc = new JSON().parse(new JSON.StringSource(response.getContentAsString()));
|
||||
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 TEXT accept
|
||||
response = client.newRequest(serverBaseURI.resolve("/demo/stats"))
|
||||
.method(HttpMethod.GET)
|
||||
.headers((headers) -> headers.add(HttpHeader.ACCEPT, "text/plain"))
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
|
||||
assertThat("Response.contentType", response.getHeaders().get(HttpHeader.CONTENT_TYPE), containsString("text/plain"));
|
||||
|
||||
String textContent = response.getContentAsString();
|
||||
assertThat(textContent, containsString("requests: "));
|
||||
assertThat(textContent, containsString("responses: "));
|
||||
assertThat(textContent, containsString("connections: "));
|
||||
assertThat(textContent, containsString("memory: "));
|
||||
|
||||
// ---------------
|
||||
// Test HTML accept
|
||||
response = client.newRequest(serverBaseURI.resolve("/demo/stats"))
|
||||
.method(HttpMethod.GET)
|
||||
.headers((headers) -> headers.add(HttpHeader.ACCEPT, "text/html"))
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
|
||||
assertThat("Response.contentType", response.getHeaders().get(HttpHeader.CONTENT_TYPE), containsString("text/html"));
|
||||
|
||||
String htmlContent = response.getContentAsString();
|
||||
// Look for things that indicate it's a well formed HTML output
|
||||
assertThat(htmlContent, containsString("<html>"));
|
||||
assertThat(htmlContent, containsString("<body>"));
|
||||
assertThat(htmlContent, containsString("<em>requests</em>: "));
|
||||
assertThat(htmlContent, containsString("<em>responses</em>: "));
|
||||
assertThat(htmlContent, containsString("<em>connections</em>: "));
|
||||
assertThat(htmlContent, containsString("<em>memory</em>: "));
|
||||
assertThat(htmlContent, containsString("</body>"));
|
||||
assertThat(htmlContent, containsString("</html>"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
|
||||
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||
version="3.1">
|
||||
<display-name>stats-demo</display-name>
|
||||
<servlet>
|
||||
<servlet-name>Stats</servlet-name>
|
||||
<servlet-class>org.eclipse.jetty.servlet.StatisticsServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Stats</servlet-name>
|
||||
<url-pattern>/stats</url-pattern>
|
||||
</servlet-mapping>
|
||||
</web-app>
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>stats-demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Stats Demo</h1>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue