org.eclipse.jetty.servlet.StatisticsServlet Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.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.servlet;
import java.io.IOException;
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.Connector;
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.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.
*
* You can use normal HTTP content negotiation to ask for the statistics.
* Specify a request Accept
header for one of the following formats:
*
*
* application/json
* text/xml
* text/html
* text/plain
- default if no Accept
header specified
*
*/
public class StatisticsServlet extends HttpServlet
{
private static final Logger LOG = LoggerFactory.getLogger(StatisticsServlet.class);
boolean _restrictToLocalhost = true; // defaults to true
private StatisticsHandler _statsHandler;
private MemoryMXBean _memoryBean;
private List _connectors;
@Override
public void init() throws ServletException
{
ServletContext context = getServletContext();
ContextHandler.Context scontext = (ContextHandler.Context)context;
Server server = scontext.getContextHandler().getServer();
_statsHandler = server.getChildHandlerByClass(StatisticsHandler.class);
if (_statsHandler == null)
{
LOG.warn("Statistics Handler not installed!");
return;
}
_memoryBean = ManagementFactory.getMemoryMXBean();
_connectors = Arrays.asList(server.getConnectors());
if (getInitParameter("restrictToLocalhost") != null)
{
_restrictToLocalhost = "true".equals(getInitParameter("restrictToLocalhost"));
}
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
if (_statsHandler == null)
{
LOG.warn("Statistics Handler not installed!");
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return;
}
if (_restrictToLocalhost)
{
if (!isLoopbackAddress(request.getRemoteAddr()))
{
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
}
if (Boolean.parseBoolean(request.getParameter("statsReset")))
{
response.setStatus(HttpServletResponse.SC_OK);
_statsHandler.statsReset();
return;
}
if (request.getParameter("xml") != null)
{
LOG.warn("'xml' parameter is deprecated, use 'Accept' request header instead");
}
List acceptable = getOrderedAcceptableMimeTypes(request);
for (String mimeType : acceptable)
{
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;
}
}
// 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 = 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)
{
values.addValue(acceptParameter);
}
Enumeration 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)
{
try
{
InetAddress addr = InetAddress.getByName(address);
return addr.isLoopbackAddress();
}
catch (UnknownHostException e)
{
LOG.warn("Warning: attempt to access statistics servlet from {}", address, e);
return false;
}
}
private CharSequence generateResponse(OutputProducer outputProducer)
{
Map top = new HashMap<>();
// requests
Map requests = new HashMap<>();
requests.put("statsOnMs", _statsHandler.getStatsOnMs());
requests.put("requests", _statsHandler.getRequests());
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());
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());
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());
requests.put("errors", _statsHandler.getErrors());
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