(tomcat) branch 10.1.x updated: Use reflection to load clustering classes in sessionsList.jsp so the sessions list page renders correctly when clustering JARs are not present at runtime

[email protected]
Newsgroups gmane.comp.jakarta.tomcat.devel
Message-ID <178615527803.2721362.15198358075702349006@gitbox3-he-fi.apache.org>
This is an automated email from the ASF dual-hosted git repository.

csutherl pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/10.1.x by this push:
     new 3ec53ba607 Use reflection to load clustering classes in sessionsList.jsp so the sessions list page renders correctly when clustering JARs are not present at runtime
3ec53ba607 is described below

commit 3ec53ba607f8b6efc4ad48f7529469aa62e5a1cf
Author: Coty Sutherland <[email protected]>
AuthorDate: Fri Aug 7 21:59:38 2026 -0400

    Use reflection to load clustering classes in sessionsList.jsp so the sessions list page renders correctly when clustering JARs are not present at runtime
---
 .../apache/catalina/manager/TestManagerWebapp.java | 86 ++++++++++++++++++++++
 webapps/docs/changelog.xml                         |  4 +
 webapps/manager/WEB-INF/jsp/sessionsList.jsp       | 35 +++++++--
 3 files changed, 119 insertions(+), 6 deletions(-)

diff --git a/test/org/apache/catalina/manager/TestManagerWebapp.java b/test/org/apache/catalina/manager/TestManagerWebapp.java
index 9ea01d30db..9892069883 100644
--- a/test/org/apache/catalina/manager/TestManagerWebapp.java
+++ b/test/org/apache/catalina/manager/TestManagerWebapp.java
@@ -22,6 +22,8 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import jakarta.servlet.http.HttpServletResponse;
 
@@ -34,6 +36,7 @@ import org.apache.catalina.Lifecycle;
 import org.apache.catalina.LifecycleEvent;
 import org.apache.catalina.LifecycleListener;
 import org.apache.catalina.authenticator.TestBasicAuthParser.BasicAuthHeader;
+import org.apache.catalina.filters.Constants;
 import org.apache.catalina.realm.MemoryRealm;
 import org.apache.catalina.realm.MessageDigestCredentialHandler;
 import org.apache.catalina.startup.Catalina;
@@ -614,6 +617,89 @@ public class TestManagerWebapp extends TomcatBaseTest {
         }
     }
 
+    /**
+     * Verify that manager JSPs compile and render correctly.
+     */
+    @Test
+    public void testJsps() throws Exception {
+        ignoreTearDown = true;
+        Tomcat tomcat = getTomcatInstance();
+        tomcat.addUser("admin", "sekr3t");
+        tomcat.addRole("admin", "manager-gui");
+
+        File webappDir = new File(getBuildDirectory(), "webapps");
+        File appDir = new File(webappDir, "manager");
+        tomcat.addWebapp(null, "/manager", appDir.getAbsolutePath());
+
+        Context ctx = tomcat.addContext("/testapp", null);
+        Tomcat.addServlet(ctx, "default", new org.apache.catalina.servlets.DefaultServlet());
+        ctx.addServletMappingDecoded("/", "default");
+
+        tomcat.start();
+
+        SimpleHttpClient client = new SimpleHttpClient() {
+            @Override
+            public boolean isResponseBodyOK() {
+                return true;
+            }
+        };
+        client.setPort(getPort());
+        String basicHeader =
+                (new BasicAuthHeader("Basic", "admin", "sekr3t")).getHeader().toString();
+
+        // Hit the HTML manager entry point to get a session and CSRF nonce
+        // @formatter:off
+        client.setRequest(new String[] {
+                "GET /manager/html HTTP/1.1" + CRLF +
+                    "Host: localhost" + CRLF +
+                    "Authorization: " + basicHeader + CRLF +
+                    "Connection: Close" + CRLF +
+                    CRLF
+                });
+        // @formatter:on
+        client.connect();
+        client.processRequest(true);
+        Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
+
+        String body = client.getResponseBody();
+        Pattern noncePattern = Pattern.compile(
+                Pattern.quote(Constants.CSRF_NONCE_REQUEST_PARAM) + "=([A-F0-9]+)");
+        Matcher m = noncePattern.matcher(body);
+        Assert.assertTrue("CSRF nonce not found in manager HTML response", m.find());
+        String nonce = m.group(1);
+
+        String sessionCookie = null;
+        for (String header : client.getResponseHeaders()) {
+            if (header.startsWith("Set-Cookie:")) {
+                String cookieValue = header.substring("Set-Cookie:".length()).trim();
+                sessionCookie = cookieValue.split(";")[0];
+                break;
+            }
+        }
+        Assert.assertNotNull("Session cookie not found", sessionCookie);
+
+        // Access sessions list page with the CSRF nonce
+        // @formatter:off
+        client.setRequest(new String[] {
+                "GET /manager/html/sessions?path=/testapp&" +
+                    Constants.CSRF_NONCE_REQUEST_PARAM + "=" + nonce +
+                    " HTTP/1.1" + CRLF +
+                    "Host: localhost" + CRLF +
+                    "Authorization: " + basicHeader + CRLF +
+                    "Cookie: " + sessionCookie + CRLF +
+                    "Connection: Close" + CRLF +
+                    CRLF
+                });
+        // @formatter:on
+        client.connect();
+        client.processRequest(true);
+        Assert.assertEquals(HttpServletResponse.SC_OK, client.getStatusCode());
+        Assert.assertTrue(client.getResponseBody().contains("Sessions Administration"));
+        Assert.assertTrue(client.getResponseBody().contains("active Sessions"));
+
+        tomcat.stop();
+    }
+
     private static class FailOnceListener implements LifecycleListener {
         private volatile boolean firstRun = true;
         @Override
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 03756e8c98..4762c1f700 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -260,6 +260,10 @@
         Documentation: Expand the description of the <code>%S</code> (session ID)
         access log pattern token. (markt)
       </fix>
+      <fix>
+        Manager: Use reflection to load clustering classes in
+        <code>sessionsList.jsp</code> so the sessions list page renders
+        correctly when clustering JARs are not present. (csutherl)
     </changelog>
   </subsection>
   <subsection name="Other">
diff --git a/webapps/manager/WEB-INF/jsp/sessionsList.jsp b/webapps/manager/WEB-INF/jsp/sessionsList.jsp
index b4ae54d710..8742115d71 100644
--- a/webapps/manager/WEB-INF/jsp/sessionsList.jsp
+++ b/webapps/manager/WEB-INF/jsp/sessionsList.jsp
@@ -19,14 +19,33 @@
 <%@page import="java.util.Collection" %>
 <%@page import="org.apache.catalina.manager.JspHelper" %>
 <%@page import="org.apache.catalina.Session" %>
-<%@page import="org.apache.catalina.ha.session.DeltaSession" %>
 <%@page import="org.apache.catalina.util.ContextName" %>
 <!DOCTYPE html
      PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
 
-<%@page import="org.apache.catalina.manager.DummyProxySession"%><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<%@page import="java.lang.reflect.Method"%>
+<%@page import="org.apache.catalina.manager.DummyProxySession"%>
+<%!
+private static final Class<?> deltaSessionClass;
+private static final Method isPrimaryMethod;
+static {
+    Class<?> clazz = null;
+    Method method = null;
+    try {
+        clazz = Class.forName("org.apache.catalina.ha.session.DeltaSession");
+        method = clazz.getMethod("isPrimarySession");
+    } catch (ClassNotFoundException e) {
+        // Expected when clustering JARs are not present
+    } catch (NoSuchMethodException e) {
+        // Should not happen if the class is available
+    }
+    deltaSessionClass = clazz;
+    isPrimaryMethod = method;
+}
+%>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <% String path = (String) request.getAttribute("path");
    String version = (String) request.getAttribute("version");
    ContextName cn = new ContextName(path, version);
@@ -104,11 +123,15 @@
     for (Session currentSession : activeSessions) {
        String currentSessionId = JspHelper.escapeXml(currentSession.getId());
        String type;
-       if (currentSession instanceof DeltaSession) {
-           if (((DeltaSession) currentSession).isPrimarySession()) {
+       if (deltaSessionClass != null && deltaSessionClass.isInstance(currentSession)) {
+           try {
+               if (Boolean.TRUE.equals(isPrimaryMethod.invoke(currentSession))) {
+                   type = "Primary";
+               } else {
+                   type = "Backup";
+               }
+           } catch (Exception e) {
                type = "Primary";
-           } else {
-               type = "Backup";
            }
        } else if (currentSession instanceof DummyProxySession) {
            type = "Proxy";
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.