(tomcat) branch 11.0.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 <178615514913.2716706.12490629740715622051@gitbox3-he-fi.apache.org>
This is an automated email from the ASF dual-hosted git repository.

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


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

commit 5a4515fcce94bfb8a71d9c938d43166eab85e62a
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 3f281bb52e..1766eaa45c 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 7b5453cf68..629162bdca 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -264,6 +264,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.