This is an automated email from the ASF dual-hosted git repository. dsoumis pushed a commit to branch 11.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 6e5eb3b1bf7cd3585d449ae59d5308f4a53973d1 Author: Dimitris Soumis <[email protected]> AuthorDate: Tue Jun 9 14:08:10 2026 +0300 Add showReport attribute in JsonErrorReportValve. --- .../catalina/valves/JsonErrorReportValve.java | 124 +++++++++++---------- .../catalina/valves/TestJsonErrorReportValve.java | 77 +++++++++++++ webapps/docs/config/valve.xml | 9 ++ 3 files changed, 152 insertions(+), 58 deletions(-) diff --git a/java/org/apache/catalina/valves/JsonErrorReportValve.java b/java/org/apache/catalina/valves/JsonErrorReportValve.java index 9838b8e2ae..63e62a2917 100644 --- a/java/org/apache/catalina/valves/JsonErrorReportValve.java +++ b/java/org/apache/catalina/valves/JsonErrorReportValve.java @@ -61,73 +61,81 @@ public class JsonErrorReportValve extends ErrorReportValve { return; } - StringManager smClient = StringManager.getManager(Constants.Package, request.getLocales()); - response.setLocale(smClient.getLocale()); - String type; - if (throwable != null) { - type = smClient.getString("errorReportValve.exceptionReport"); - } else { - type = smClient.getString("errorReportValve.statusReport"); - } - String message = response.getMessage(); - if (message == null && throwable != null) { - message = throwable.getMessage(); - } - if (message == null) { - message = ""; - } - String description = smClient.getString("http." + statusCode + ".desc"); - if (description == null) { - if (message.isEmpty()) { - return; + StringBuilder sb = new StringBuilder(); + sb.append("{\n"); + sb.append(" \"status\": ").append(statusCode); + + if (isShowReport()) { + StringManager smClient = StringManager.getManager(Constants.Package, request.getLocales()); + response.setLocale(smClient.getLocale()); + String type; + if (throwable != null) { + type = smClient.getString("errorReportValve.exceptionReport"); } else { - description = smClient.getString("errorReportValve.noDescription"); + type = smClient.getString("errorReportValve.statusReport"); } - } - StringBuilder sb = new StringBuilder(); - sb.append("{\n \"type\": \"").append(JSONFilter.escape(type)).append("\",\n"); - sb.append(" \"status\": ").append(statusCode).append(",\n"); - sb.append(" \"message\": \"").append(JSONFilter.escape(message)).append("\",\n"); - sb.append(" \"description\": \"").append(JSONFilter.escape(description)); - - if (throwable != null) { - sb.append("\",\n"); - - // Stack trace - sb.append(" \"throwable\": ["); - int loops = 0; - boolean first = true; - do { - if (!first) { - sb.append(','); + String message = response.getMessage(); + if (message == null && throwable != null) { + message = throwable.getMessage(); + } + if (message == null) { + message = ""; + } + String description = smClient.getString("http." + statusCode + ".desc"); + if (description == null) { + if (message.isEmpty()) { + return; } else { - first = false; + description = smClient.getString("errorReportValve.noDescription"); } - sb.append('\"').append(JSONFilter.escape(throwable.toString())).append('\"'); - - StackTraceElement[] elements = throwable.getStackTrace(); - int pos = elements.length; - for (int i = elements.length - 1; i >= 0; i--) { - if ((elements[i].getClassName().startsWith("org.apache.catalina.core.ApplicationFilterChain")) && - (elements[i].getMethodName().equals("doFilter"))) { - pos = i; - break; + } + sb.append(",\n"); + sb.append(" \"type\": \"").append(JSONFilter.escape(type)).append("\",\n"); + sb.append(" \"message\": \"").append(JSONFilter.escape(message)).append("\",\n"); + sb.append(" \"description\": \"").append(JSONFilter.escape(description)); + + if (throwable != null) { + sb.append("\",\n"); + + // Stack trace + sb.append(" \"throwable\": ["); + int loops = 0; + boolean first = true; + do { + if (!first) { + sb.append(','); + } else { + first = false; } - } - for (int i = 0; i < pos; i++) { - if (!(elements[i].getClassName().startsWith("org.apache.catalina.core."))) { - sb.append(',').append('\"').append(' ').append(JSONFilter.escape(elements[i].toString())) - .append('\"'); + sb.append('\"').append(JSONFilter.escape(throwable.toString())).append('\"'); + + StackTraceElement[] elements = throwable.getStackTrace(); + int pos = elements.length; + for (int i = elements.length - 1; i >= 0; i--) { + if (elements[i].getClassName() + .startsWith("org.apache.catalina.core.ApplicationFilterChain") && + elements[i].getMethodName().equals("doFilter")) { + pos = i; + break; + } + } + for (int i = 0; i < pos; i++) { + if (!elements[i].getClassName().startsWith("org.apache.catalina.core.")) { + sb.append(',').append('\"').append(' ') + .append(JSONFilter.escape(elements[i].toString())).append('\"'); + } } - } - throwable = throwable.getCause(); - loops++; - } while (throwable != null && (loops < 10)); - sb.append("]\n}"); + throwable = throwable.getCause(); + loops++; + } while (throwable != null && loops < 10); + sb.append("]\n}"); + } else { + sb.append("\"\n}"); + } } else { - sb.append("\"\n}"); + sb.append("\n}"); } try { diff --git a/test/org/apache/catalina/valves/TestJsonErrorReportValve.java b/test/org/apache/catalina/valves/TestJsonErrorReportValve.java index 4770a93802..c3c4b316bf 100644 --- a/test/org/apache/catalina/valves/TestJsonErrorReportValve.java +++ b/test/org/apache/catalina/valves/TestJsonErrorReportValve.java @@ -17,6 +17,7 @@ package org.apache.catalina.valves; import java.io.IOException; +import java.io.Serial; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; @@ -34,6 +35,7 @@ import org.junit.Assert; import org.junit.Test; import org.apache.catalina.Context; +import org.apache.catalina.Valve; import org.apache.catalina.core.StandardHost; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; @@ -338,6 +340,79 @@ public class TestJsonErrorReportValve extends TomcatBaseTest { Assert.assertEquals(unicodeMessage, json.get("message")); } + @Test + public void testJsonErrorShowReportFalse() throws Exception { + Tomcat tomcat = getTomcatInstance(); + StandardHost host = (StandardHost) tomcat.getHost(); + host.setErrorReportValveClass(JSON_VALVE); + + Context ctx = getProgrammaticRootContext(); + + Tomcat.addServlet(ctx, "sendError", new SendErrorServlet( + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Server broke")); + ctx.addServletMappingDecoded("/", "sendError"); + + tomcat.start(); + + for (Valve valve : host.getPipeline().getValves()) { + if (valve instanceof JsonErrorReportValve) { + ((JsonErrorReportValve) valve).setShowReport(false); + break; + } + } + + ByteChunk res = new ByteChunk(); + int rc = getUrl("http://localhost:" + getPort(), res, null); + + Assert.assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, rc); + + String body = res.toString(); + JSONParser parser = new JSONParser(body); + LinkedHashMap<String, Object> json = parser.parseObject(); + + Assert.assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ((Number) json.get("status")).intValue()); + Assert.assertNull(json.get("type")); + Assert.assertNull(json.get("message")); + Assert.assertNull(json.get("description")); + } + + + @Test + public void testJsonErrorWithThrowableShowReportFalse() throws Exception { + Tomcat tomcat = getTomcatInstance(); + StandardHost host = (StandardHost) tomcat.getHost(); + host.setErrorReportValveClass(JSON_VALVE); + + Context ctx = getProgrammaticRootContext(); + + Tomcat.addServlet(ctx, "exception", new ExceptionServlet("Something went wrong")); + ctx.addServletMappingDecoded("/", "exception"); + + tomcat.start(); + + for (Valve valve : host.getPipeline().getValves()) { + if (valve instanceof JsonErrorReportValve) { + ((JsonErrorReportValve) valve).setShowReport(false); + break; + } + } + + ByteChunk res = new ByteChunk(); + int rc = getUrl("http://localhost:" + getPort(), res, null); + + Assert.assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, rc); + + String body = res.toString(); + JSONParser parser = new JSONParser(body); + LinkedHashMap<String, Object> json = parser.parseObject(); + + Assert.assertEquals(500, ((Number) json.get("status")).intValue()); + Assert.assertNull(json.get("throwable")); + Assert.assertNull(json.get("message")); + Assert.assertNull(json.get("description")); + } + + @Test public void testNoJsonForUnknownStatusWithoutMessage() throws Exception { Tomcat tomcat = getTomcatInstance(); @@ -385,6 +460,7 @@ public class TestJsonErrorReportValve extends TomcatBaseTest { private static final class ExceptionServlet extends HttpServlet { + @Serial private static final long serialVersionUID = 1L; private final String message; @@ -401,6 +477,7 @@ public class TestJsonErrorReportValve extends TomcatBaseTest { private static final class ChainedExceptionServlet extends HttpServlet { + @Serial private static final long serialVersionUID = 1L; @Override diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml index b31d701d3e..971c378ca9 100644 --- a/webapps/docs/config/valve.xml +++ b/webapps/docs/config/valve.xml @@ -2447,6 +2447,15 @@ <strong>org.apache.catalina.valves.JsonErrorReportValve</strong>.</p> </attribute> + <attribute name="showReport" required="false"> + <p>Flag to determine if the error report (custom error message, + description and/or stack trace) is included in the JSON response + when an error occurs. If set to <code>false</code>, then the JSON + response will only contain the HTTP status code. + Default value: <code>true</code> + </p> + </attribute> + </attributes> </subsection> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
