This is an automated email from the ASF dual-hosted git repository.

remm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new c7b5cf882f Add JSON stats to the status servlet
c7b5cf882f is described below

commit c7b5cf882fe847b3bc495d46bf188f8614be7b3e
Author: remm <r...@apache.org>
AuthorDate: Tue Oct 17 15:57:46 2023 +0200

    Add JSON stats to the status servlet
    
    The format is easy to parse and validate and is a straight equivalent to
    the XML view.
    I did not add the detailed processor view, it seems way to verbose these
    days.
---
 .../catalina/manager/StatusManagerServlet.java     |  54 ++---
 .../apache/catalina/manager/StatusTransformer.java | 264 ++++++++++++++++++---
 .../catalina/manager/TestStatusTransformer.java    |  82 +++++++
 webapps/docs/changelog.xml                         |   5 +
 webapps/docs/manager-howto.xml                     |   5 +
 5 files changed, 345 insertions(+), 65 deletions(-)

diff --git a/java/org/apache/catalina/manager/StatusManagerServlet.java 
b/java/org/apache/catalina/manager/StatusManagerServlet.java
index ab6e8010ee..912973117a 100644
--- a/java/org/apache/catalina/manager/StatusManagerServlet.java
+++ b/java/org/apache/catalina/manager/StatusManagerServlet.java
@@ -170,12 +170,16 @@ public class StatusManagerServlet extends HttpServlet 
implements NotificationLis
 
         StringManager smClient = StringManager.getManager(Constants.Package, 
request.getLocales());
 
-        // mode is flag for HTML or XML output
+        // mode is flag for HTML, JSON or XML output
         int mode = 0;
         // if ?XML=true, set the mode to XML
         if (request.getParameter("XML") != null && 
request.getParameter("XML").equals("true")) {
             mode = 1;
         }
+        // if ?JSON=true, set the mode to JSON
+        if (request.getParameter("JSON") != null && 
request.getParameter("JSON").equals("true")) {
+            mode = 2;
+        }
         StatusTransformer.setContentType(response, mode);
 
         PrintWriter writer = response.getWriter();
@@ -271,32 +275,28 @@ public class StatusManagerServlet extends HttpServlet 
implements NotificationLis
             // use StatusTransformer to output status
             StatusTransformer.writeVMState(writer, mode, args);
 
-            for (ObjectName objectName : threadPools) {
-                String name = objectName.getKeyProperty("name");
-                args = new Object[19];
-                args[0] = 
smClient.getString("htmlManagerServlet.connectorStateMaxThreads");
-                args[1] = 
smClient.getString("htmlManagerServlet.connectorStateThreadCount");
-                args[2] = 
smClient.getString("htmlManagerServlet.connectorStateThreadBusy");
-                args[3] = 
smClient.getString("htmlManagerServlet.connectorStateAliveSocketCount");
-                args[4] = 
smClient.getString("htmlManagerServlet.connectorStateMaxProcessingTime");
-                args[5] = 
smClient.getString("htmlManagerServlet.connectorStateProcessingTime");
-                args[6] = 
smClient.getString("htmlManagerServlet.connectorStateRequestCount");
-                args[7] = 
smClient.getString("htmlManagerServlet.connectorStateErrorCount");
-                args[8] = 
smClient.getString("htmlManagerServlet.connectorStateBytesReceived");
-                args[9] = 
smClient.getString("htmlManagerServlet.connectorStateBytesSent");
-                args[10] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleStage");
-                args[11] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleTime");
-                args[12] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleBSent");
-                args[13] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleBRecv");
-                args[14] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleClientForw");
-                args[15] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleClientAct");
-                args[16] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleVHost");
-                args[17] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleRequest");
-                args[18] = 
smClient.getString("htmlManagerServlet.connectorStateHint");
-                // use StatusTransformer to output status
-                StatusTransformer.writeConnectorState(writer, objectName, 
name, mBeanServer, globalRequestProcessors,
-                        requestProcessors, mode, args);
-            }
+            args = new Object[19];
+            args[0] = 
smClient.getString("htmlManagerServlet.connectorStateMaxThreads");
+            args[1] = 
smClient.getString("htmlManagerServlet.connectorStateThreadCount");
+            args[2] = 
smClient.getString("htmlManagerServlet.connectorStateThreadBusy");
+            args[3] = 
smClient.getString("htmlManagerServlet.connectorStateAliveSocketCount");
+            args[4] = 
smClient.getString("htmlManagerServlet.connectorStateMaxProcessingTime");
+            args[5] = 
smClient.getString("htmlManagerServlet.connectorStateProcessingTime");
+            args[6] = 
smClient.getString("htmlManagerServlet.connectorStateRequestCount");
+            args[7] = 
smClient.getString("htmlManagerServlet.connectorStateErrorCount");
+            args[8] = 
smClient.getString("htmlManagerServlet.connectorStateBytesReceived");
+            args[9] = 
smClient.getString("htmlManagerServlet.connectorStateBytesSent");
+            args[10] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleStage");
+            args[11] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleTime");
+            args[12] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleBSent");
+            args[13] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleBRecv");
+            args[14] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleClientForw");
+            args[15] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleClientAct");
+            args[16] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleVHost");
+            args[17] = 
smClient.getString("htmlManagerServlet.connectorStateTableTitleRequest");
+            args[18] = 
smClient.getString("htmlManagerServlet.connectorStateHint");
+            StatusTransformer.writeConnectorsState(writer, mBeanServer, 
threadPools, globalRequestProcessors,
+                    requestProcessors, mode, args);
 
             if (request.getPathInfo() != null && 
request.getPathInfo().equals("/all")) {
                 // Note: Retrieving the full status is much slower
diff --git a/java/org/apache/catalina/manager/StatusTransformer.java 
b/java/org/apache/catalina/manager/StatusTransformer.java
index 1d51ea1713..e4fbf52ca7 100644
--- a/java/org/apache/catalina/manager/StatusTransformer.java
+++ b/java/org/apache/catalina/manager/StatusTransformer.java
@@ -34,6 +34,7 @@ import javax.management.ObjectName;
 
 import jakarta.servlet.http.HttpServletResponse;
 
+import org.apache.tomcat.util.json.JSONFilter;
 import org.apache.tomcat.util.security.Escape;
 
 /**
@@ -51,6 +52,8 @@ public class StatusTransformer {
             response.setContentType("text/html;charset=" + Constants.CHARSET);
         } else if (mode == 1) {
             response.setContentType("text/xml;charset=" + Constants.CHARSET);
+        } else if (mode == 2) {
+            response.setContentType("application/json");
         }
     }
 
@@ -60,7 +63,7 @@ public class StatusTransformer {
      *
      * @param writer the PrintWriter to use
      * @param args   Path prefix for URLs
-     * @param mode   - 0 = HTML header, 1 = XML declaration
+     * @param mode   - 0 = HTML header, 1 = XML declaration, 2 = JSON
      */
     public static void writeHeader(PrintWriter writer, Object[] args, int 
mode) {
         if (mode == 0) {
@@ -70,6 +73,8 @@ public class StatusTransformer {
             writer.write(Constants.XML_DECLARATION);
             writer.print(MessageFormat.format(Constants.XML_STYLE, args));
             writer.write("<status>");
+        } else if (mode == 2) {
+            
writer.append('{').append('"').append("tomcat").append('"').append(':').append('{').println();
         }
     }
 
@@ -122,6 +127,8 @@ public class StatusTransformer {
             writer.print(Constants.HTML_TAIL_SECTION);
         } else if (mode == 1) {
             writer.write("</status>");
+        } else if (mode == 2) {
+            writer.append('}').append('}');
         }
     }
 
@@ -203,11 +210,87 @@ public class StatusTransformer {
             }
 
             writer.write("</jvm>");
+        } else if (mode == 2) {
+            
writer.append('"').append("jvm").append('"').append(':').append('{').println();
+
+            
writer.append('"').append("memory").append('"').append(':').append('{');
+            appendJSonValue(writer, "free", 
Long.toString(Runtime.getRuntime().freeMemory())).append(',');
+            appendJSonValue(writer, "total", 
Long.toString(Runtime.getRuntime().totalMemory())).append(',');
+            appendJSonValue(writer, "max", 
Long.toString(Runtime.getRuntime().maxMemory()));
+            writer.append('}').append(',').println();
+
+            
writer.append('"').append("memorypool").append('"').append(':').append('[');
+            boolean first = true;
+            for (MemoryPoolMXBean memoryPoolMBean : memoryPoolMBeans.values()) 
{
+                MemoryUsage usage = memoryPoolMBean.getUsage();
+                if (first) {
+                    first = false;
+                } else {
+                    writer.append(',').println();
+                }
+                writer.append('{');
+                appendJSonValue(writer, "name", 
JSONFilter.escape(memoryPoolMBean.getName())).append(',');
+                appendJSonValue(writer, "type", 
memoryPoolMBean.getType().toString()).append(',');
+                appendJSonValue(writer, "usageInit", 
Long.toString(usage.getInit())).append(',');
+                appendJSonValue(writer, "usageCommitted", 
Long.toString(usage.getCommitted())).append(',');
+                appendJSonValue(writer, "usageMax", 
Long.toString(usage.getMax())).append(',');
+                appendJSonValue(writer, "usageUsed", 
Long.toString(usage.getUsed()));
+                writer.append('}');
+            }
+            writer.append(']').println();
+
+            writer.append('}');
         }
 
     }
 
 
+    private static PrintWriter appendJSonValue(PrintWriter writer, String 
name, String value) {
+        return 
writer.append('"').append(name).append('"').append(':').append('"').append(value).append('"');
+    }
+
+
+    /**
+     * Write connector state.
+     *
+     * @param writer                  The output writer
+     * @param mBeanServer             MBean server
+     * @param threadPools             MBean names for the thread pools of the 
connectors
+     * @param globalRequestProcessors MBean names for the global request 
processors
+     * @param requestProcessors       MBean names for the request processors
+     * @param mode                    Mode <code>0</code> will generate HTML. 
Mode <code>1</code> will generate XML.
+     * @param args                    I18n labels for the Connector state 
values
+     *
+     * @throws Exception Propagated JMX error
+     */
+    public static void writeConnectorsState(PrintWriter writer, MBeanServer 
mBeanServer,
+            List<ObjectName> threadPools,
+            List<ObjectName> globalRequestProcessors, List<ObjectName> 
requestProcessors, int mode, Object[] args)
+            throws Exception {
+        if (mode == 2) {
+            writer.append(',').println();
+            
writer.append('"').append("connector").append('"').append(':').append('[').println();
+        }
+        boolean first = true;
+        for (ObjectName objectName : threadPools) {
+            if (first) {
+                first = false;
+            } else {
+                if (mode == 2) {
+                    writer.append(',').println();
+                }
+            }
+            String name = objectName.getKeyProperty("name");
+            // use StatusTransformer to output status
+            StatusTransformer.writeConnectorState(writer, objectName, name, 
mBeanServer, globalRequestProcessors,
+                    requestProcessors, mode, args);
+        }
+        if (mode == 2) {
+            writer.append(']').println();
+        }
+    }
+
+
     /**
      * Write connector state.
      *
@@ -344,6 +427,38 @@ public class StatusTransformer {
             }
 
             writer.write("</connector>");
+        } else if (mode == 2) {
+            writer.append('{').println();
+
+            appendJSonValue(writer, "name", 
JSONFilter.escape(name)).append(',').println();
+            
writer.append('"').append("threadInfo").append('"').append(':').append('{');
+            appendJSonValue(writer, "maxThreads", 
mBeanServer.getAttribute(tpName, "maxThreads").toString()).append(',');
+            appendJSonValue(writer, "currentThreadCount", 
mBeanServer.getAttribute(tpName, "currentThreadCount").toString()).append(',');
+            appendJSonValue(writer, "currentThreadsBusy", 
mBeanServer.getAttribute(tpName, "currentThreadsBusy").toString());
+            writer.append('}');
+
+            ObjectName grpName = null;
+            for (ObjectName objectName : globalRequestProcessors) {
+                // Find the HTTP/1.1 RequestGroupInfo - BZ 65404
+                if (name.equals(objectName.getKeyProperty("name")) && 
objectName.getKeyProperty("Upgrade") == null) {
+                    grpName = objectName;
+                }
+            }
+
+            if (grpName != null) {
+                writer.append(',').println();
+                
writer.append('"').append("requestInfo").append('"').append(':').append('{');
+                appendJSonValue(writer, "maxTime", 
mBeanServer.getAttribute(grpName, "maxTime").toString()).append(',');
+                appendJSonValue(writer, "processingTime", 
mBeanServer.getAttribute(grpName, "processingTime").toString()).append(',');
+                appendJSonValue(writer, "requestCount", 
mBeanServer.getAttribute(grpName, "requestCount").toString()).append(',');
+                appendJSonValue(writer, "errorCount", 
mBeanServer.getAttribute(grpName, "errorCount").toString()).append(',');
+                appendJSonValue(writer, "bytesReceived", 
mBeanServer.getAttribute(grpName, "bytesReceived").toString()).append(',');
+                appendJSonValue(writer, "bytesSent", 
mBeanServer.getAttribute(grpName, "bytesSent").toString());
+                writer.append('}').println();
+                // Note: No detailed per processor info
+            }
+
+            writer.append('}');
         }
     }
 
@@ -530,9 +645,9 @@ public class StatusTransformer {
      */
     public static void writeDetailedState(PrintWriter writer, MBeanServer 
mBeanServer, int mode) throws Exception {
 
+        ObjectName queryHosts = new ObjectName("*:j2eeType=WebModule,*");
+        Set<ObjectName> hostsON = mBeanServer.queryNames(queryHosts, null);
         if (mode == 0) {
-            ObjectName queryHosts = new ObjectName("*:j2eeType=WebModule,*");
-            Set<ObjectName> hostsON = mBeanServer.queryNames(queryHosts, null);
 
             // Navigation menu
             writer.print("<h1>");
@@ -575,6 +690,21 @@ public class StatusTransformer {
 
         } else if (mode == 1) {
             // for now we don't write out the Detailed state in XML
+        } else if (mode == 2) {
+            writer.append(',').println();
+            
writer.append('"').append("context").append('"').append(':').append('[');
+            Iterator<ObjectName> iterator = hostsON.iterator();
+            boolean first = true;
+            while (iterator.hasNext()) {
+                if (first) {
+                    first = false;
+                } else {
+                    writer.append(',').println();
+                }
+                ObjectName contextON = iterator.next();
+                writeContext(writer, contextON, mBeanServer, mode);
+            }
+            writer.append(']').println();
         }
 
     }
@@ -593,42 +723,43 @@ public class StatusTransformer {
     protected static void writeContext(PrintWriter writer, ObjectName 
objectName, MBeanServer mBeanServer, int mode)
             throws Exception {
 
-        if (mode == 0) {
-            String webModuleName = objectName.getKeyProperty("name");
-            String name = webModuleName;
-            if (name == null) {
-                return;
-            }
+        String webModuleName = objectName.getKeyProperty("name");
+        String name = webModuleName;
+        if (name == null) {
+            return;
+        }
 
-            String hostName = null;
-            String contextName = null;
-            if (name.startsWith("//")) {
-                name = name.substring(2);
-            }
-            int slash = name.indexOf('/');
-            if (slash != -1) {
-                hostName = name.substring(0, slash);
-                contextName = name.substring(slash);
-            } else {
-                return;
-            }
+        String hostName = null;
+        String contextName = null;
+        if (name.startsWith("//")) {
+            name = name.substring(2);
+        }
+        int slash = name.indexOf('/');
+        if (slash != -1) {
+            hostName = name.substring(0, slash);
+            contextName = name.substring(slash);
+        } else {
+            return;
+        }
 
-            ObjectName queryManager = new ObjectName(
-                    objectName.getDomain() + ":type=Manager,context=" + 
contextName + ",host=" + hostName + ",*");
-            Set<ObjectName> managersON = mBeanServer.queryNames(queryManager, 
null);
-            ObjectName managerON = null;
-            for (ObjectName aManagersON : managersON) {
-                managerON = aManagersON;
-            }
+        ObjectName queryManager = new ObjectName(
+                objectName.getDomain() + ":type=Manager,context=" + 
contextName + ",host=" + hostName + ",*");
+        Set<ObjectName> managersON = mBeanServer.queryNames(queryManager, 
null);
+        ObjectName managerON = null;
+        for (ObjectName aManagersON : managersON) {
+            managerON = aManagersON;
+        }
 
-            ObjectName queryJspMonitor =
-                    new ObjectName(objectName.getDomain() + 
":type=JspMonitor,WebModule=" + webModuleName + ",*");
-            Set<ObjectName> jspMonitorONs = 
mBeanServer.queryNames(queryJspMonitor, null);
+        ObjectName queryJspMonitor =
+                new ObjectName(objectName.getDomain() + 
":type=JspMonitor,WebModule=" + webModuleName + ",*");
+        Set<ObjectName> jspMonitorONs = 
mBeanServer.queryNames(queryJspMonitor, null);
 
-            // Special case for the root context
-            if (contextName.equals("/")) {
-                contextName = "";
-            }
+        // Special case for the root context
+        if (contextName.equals("/")) {
+            contextName = "";
+        }
+
+        if (mode == 0) {
 
             writer.print("<h1>");
             writer.print(Escape.htmlElementContent(name));
@@ -659,6 +790,35 @@ public class StatusTransformer {
 
         } else if (mode == 1) {
             // for now we don't write out the context in XML
+        } else if (mode == 2) {
+            writer.append('{');
+            appendJSonValue(writer, "name", 
JSONFilter.escape(JSONFilter.escape(name))).append(',');
+            appendJSonValue(writer, "startTime",
+                    new Date(((Long) mBeanServer.getAttribute(objectName, 
"startTime")).longValue()).toString()).append(',');
+            appendJSonValue(writer, "startupTime", 
mBeanServer.getAttribute(objectName, "startupTime").toString()).append(',');
+            appendJSonValue(writer, "tldScanTime", 
mBeanServer.getAttribute(objectName, "tldScanTime").toString());
+            if (managerON != null) {
+                writeManager(writer, managerON, mBeanServer, mode);
+            }
+            if (jspMonitorONs != null) {
+                writeJspMonitor(writer, jspMonitorONs, mBeanServer, mode);
+            }
+            writer.append(',').println();
+            
writer.append('"').append("wrapper").append('"').append(':').append('[');
+            String onStr = objectName.getDomain() + 
":j2eeType=Servlet,WebModule=" + webModuleName + ",*";
+            ObjectName servletObjectName = new ObjectName(onStr);
+            Set<ObjectInstance> set = 
mBeanServer.queryMBeans(servletObjectName, null);
+            boolean first = true;
+            for (ObjectInstance oi : set) {
+                if (first) {
+                    first = false;
+                } else {
+                    writer.append(',').println();
+                }
+                writeWrapper(writer, oi.getObjectName(), mBeanServer, mode);
+            }
+            writer.append(']').println();
+            writer.append('}');
         }
 
     }
@@ -697,6 +857,18 @@ public class StatusTransformer {
             writer.print(formatTime(mBeanServer.getAttribute(objectName, 
"processingTime"), false));
         } else if (mode == 1) {
             // for now we don't write out the wrapper details
+        } else if (mode == 2) {
+            writer.append(',').println();
+            
writer.append('"').append("manager").append('"').append(':').append('{');
+            appendJSonValue(writer, "activeSessions", 
mBeanServer.getAttribute(objectName, "activeSessions").toString()).append(',');
+            appendJSonValue(writer, "sessionCounter", 
mBeanServer.getAttribute(objectName, "sessionCounter").toString()).append(',');
+            appendJSonValue(writer, "maxActive", 
mBeanServer.getAttribute(objectName, "maxActive").toString()).append(',');
+            appendJSonValue(writer, "rejectedSessions", 
mBeanServer.getAttribute(objectName, 
"rejectedSessions").toString()).append(',');
+            appendJSonValue(writer, "expiredSessions", 
mBeanServer.getAttribute(objectName, "expiredSessions").toString()).append(',');
+            appendJSonValue(writer, "sessionMaxAliveTime", 
mBeanServer.getAttribute(objectName, 
"sessionMaxAliveTime").toString()).append(',');
+            appendJSonValue(writer, "sessionAverageAliveTime", 
mBeanServer.getAttribute(objectName, 
"sessionAverageAliveTime").toString()).append(',');
+            appendJSonValue(writer, "processingTime", 
mBeanServer.getAttribute(objectName, "processingTime").toString());
+            writer.append('}');
         }
 
     }
@@ -733,6 +905,12 @@ public class StatusTransformer {
             writer.print(jspReloadCount);
         } else if (mode == 1) {
             // for now we don't write out anything
+        } else if (mode == 2) {
+            writer.append(',').println();
+            
writer.append('"').append("jsp").append('"').append(':').append('{');
+            appendJSonValue(writer, "jspCount", 
Integer.toString(jspCount)).append(',');
+            appendJSonValue(writer, "jspReloadCount", 
Integer.toString(jspReloadCount));
+            writer.append('}');
         }
     }
 
@@ -750,11 +928,11 @@ public class StatusTransformer {
     public static void writeWrapper(PrintWriter writer, ObjectName objectName, 
MBeanServer mBeanServer, int mode)
             throws Exception {
 
-        if (mode == 0) {
-            String servletName = objectName.getKeyProperty("name");
+        String servletName = objectName.getKeyProperty("name");
 
-            String[] mappings = (String[]) mBeanServer.invoke(objectName, 
"findMappings", null, null);
+        String[] mappings = (String[]) mBeanServer.invoke(objectName, 
"findMappings", null, null);
 
+        if (mode == 0) {
             writer.print("<h2>");
             writer.print(Escape.htmlElementContent(servletName));
             if (mappings != null && mappings.length > 0) {
@@ -785,6 +963,16 @@ public class StatusTransformer {
             writer.print("</p>");
         } else if (mode == 1) {
             // for now we don't write out the wrapper details
+        } else if (mode == 2) {
+            writer.append('{');
+            appendJSonValue(writer, "servletName", 
JSONFilter.escape(servletName)).append(',');
+            appendJSonValue(writer, "processingTime", 
mBeanServer.getAttribute(objectName, "processingTime").toString()).append(',');
+            appendJSonValue(writer, "maxTime", 
mBeanServer.getAttribute(objectName, "maxTime").toString()).append(',');
+            appendJSonValue(writer, "requestCount", 
mBeanServer.getAttribute(objectName, "requestCount").toString()).append(',');
+            appendJSonValue(writer, "errorCount", 
mBeanServer.getAttribute(objectName, "errorCount").toString()).append(',');
+            appendJSonValue(writer, "loadTime", 
mBeanServer.getAttribute(objectName, "loadTime").toString()).append(',');
+            appendJSonValue(writer, "classLoadTime", 
mBeanServer.getAttribute(objectName, "classLoadTime").toString());
+            writer.append('}');
         }
 
     }
diff --git a/test/org/apache/catalina/manager/TestStatusTransformer.java 
b/test/org/apache/catalina/manager/TestStatusTransformer.java
new file mode 100644
index 0000000000..b031004979
--- /dev/null
+++ b/test/org/apache/catalina/manager/TestStatusTransformer.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.manager;
+
+import java.io.File;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.catalina.startup.SimpleHttpClient.CRLF;
+import org.apache.catalina.Context;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.startup.SimpleHttpClient;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.json.JSONParser;
+
+public class TestStatusTransformer extends TomcatBaseTest {
+
+    @Test
+    public void testJSON() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        // Add default servlet to make some requests
+        File appDir = new File("test/webapp");
+        Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
+        ctxt.setPrivileged(true);
+        Wrapper defaultServlet = Tomcat.addServlet(ctxt, "default",
+                "org.apache.catalina.servlets.DefaultServlet");
+        defaultServlet.addInitParameter("fileEncoding", "ISO-8859-1");
+        ctxt.addServletMappingDecoded("/", "default");
+        Tomcat.addServlet(ctxt, "status", 
"org.apache.catalina.manager.StatusManagerServlet");
+        ctxt.addServletMappingDecoded("/status/*", "status");
+        ctxt.addMimeMapping("html", "text/html");
+        Context ctxt2 = tomcat.addContext("/test", null);
+        Tomcat.addServlet(ctxt2, "status", 
"org.apache.catalina.manager.StatusManagerServlet");
+        ctxt.addServletMappingDecoded("/somepath/*", "status");
+        tomcat.start();
+
+        SimpleHttpClient client = new SimpleHttpClient() {
+            @Override
+            public boolean isResponseBodyOK() {
+                return true;
+            }
+        };
+        client.setPort(getPort());
+        client.setRequest(new String[] {
+                "GET /index.html HTTP/1.1" + CRLF +
+                "Host: localhost" + CRLF +
+                "Connection: Close" + CRLF + CRLF });
+        client.connect();
+        client.processRequest(true);
+
+        client.setRequest(new String[] {
+                "GET /status/all?JSON=true HTTP/1.1" + CRLF +
+                "Host: localhost" + CRLF +
+                "Connection: Close" + CRLF + CRLF });
+        client.connect();
+        client.processRequest(true);
+        String json = client.getResponseBody();
+
+        JSONParser parser = new JSONParser(json);
+        String result = parser.parse().toString();
+        System.out.println(result);
+        Assert.assertTrue(result.contains("name=localhost/"));
+    }
+
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index f2cb8d1c79..69cc6ad255 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -111,6 +111,11 @@
         <bug>67667</bug>: <code>TLSCertificateReloadListener</code> prints 
unreadable
         rendering of <code>X509Certificate#getNotAfter()</code>. (michaelo)
       </fix>
+      <update>
+        The status servlet included in the manager webapp can now output
+        statistics as JSON, using the <code>JSON=true</code> URL parameter.
+        (remm)
+      </update>
     </changelog>
   </subsection>
   <subsection name="Other">
diff --git a/webapps/docs/manager-howto.xml b/webapps/docs/manager-howto.xml
index 6ae9da195c..f21ac584d3 100644
--- a/webapps/docs/manager-howto.xml
+++ b/webapps/docs/manager-howto.xml
@@ -1036,6 +1036,11 @@ 
http://localhost:8080/manager/status/all?XML=true</source>
 
 <p>Displays server status information in XML format.</p>
 
+<source>http://localhost:8080/manager/status?JSON=true
+http://localhost:8080/manager/status/all?JSON=true</source>
+
+<p>Displays server status information in JSON format.</p>
+
 <p>First, you have the server and JVM version number, JVM provider, OS name
 and number followed by the architecture type.</p>
 


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to