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

csutherl 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 91ac751845 Use reflection to load clustering classes in 
sessionsList.jsp so the sessions list page renders correctly when clustering 
JARs are not present at runtime
91ac751845 is described below

commit 91ac751845e853418f84bf917201c591810b3733
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                         |  5 ++
 webapps/manager/WEB-INF/jsp/sessionsList.jsp       | 35 +++++++--
 3 files changed, 120 insertions(+), 6 deletions(-)

diff --git a/test/org/apache/catalina/manager/TestManagerWebapp.java 
b/test/org/apache/catalina/manager/TestManagerWebapp.java
index 12261e9584..1e23f0d7af 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 260382eedc..27aea75843 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -460,6 +460,11 @@
         Manager: Drop session handling dedicated to extracting the locale from
         Tapestry attributes, used for locale session sorting. (remm)
       </update>
+      <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)
+      </fix>
       <!-- Entries for backport and removal before 12.0.0-M1 below this line 
-->
       <fix>
         Documentation: Better sample httpd configuration for use with SSLValve
diff --git a/webapps/manager/WEB-INF/jsp/sessionsList.jsp 
b/webapps/manager/WEB-INF/jsp/sessionsList.jsp
index f7e780a23e..82a2c7f830 100644
--- a/webapps/manager/WEB-INF/jsp/sessionsList.jsp
+++ b/webapps/manager/WEB-INF/jsp/sessionsList.jsp
@@ -18,7 +18,6 @@
 <%@page session="false" contentType="text/html; charset=UTF-8" %>
 <%@page import="java.util.Collection" %>
 <%@page import="org.apache.catalina.Session" %>
-<%@page import="org.apache.catalina.ha.session.DeltaSession" %>
 <%@page import="org.apache.catalina.manager.Constants" %>
 <%@page import="org.apache.catalina.manager.JspHelper" %>
 <%@page import="org.apache.catalina.util.ContextName" %>
@@ -27,7 +26,27 @@
      "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);
@@ -105,11 +124,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";


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to