This is an automated email from the ASF dual-hosted git repository. robertlazarski pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git
commit 921c4581fb05b8dc4fb2b921cd818c296a11c919 Author: Robert Lazarski <[email protected]> AuthorDate: Sat Feb 14 08:10:09 2026 -1000 Fix AXIS2-3879 + AXIS2-4146: Respect user-set HTTP status codes on responses The transport sender unconditionally set HTTP 500 for all fault responses, overwriting any user-specified status code (e.g., 503, 400). This change checks Constants.HTTP_RESPONSE_STATE on the message context before falling back to the default, and propagates the property through fault contexts. Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../apache/axis2/util/MessageContextBuilder.java | 3 ++ .../http/AbstractHTTPTransportSender.java | 45 +++++++++++++++- .../apache/axis2/transport/http/AxisServlet.java | 7 ++- .../transport/http/HTTPTransportSenderTest.java | 62 ++++++++++++++++++++++ .../http/mock/MockHttpServletResponse.java | 8 +-- 5 files changed, 119 insertions(+), 6 deletions(-) diff --git a/modules/kernel/src/org/apache/axis2/util/MessageContextBuilder.java b/modules/kernel/src/org/apache/axis2/util/MessageContextBuilder.java index ccc4d9b612..9bf82f965c 100644 --- a/modules/kernel/src/org/apache/axis2/util/MessageContextBuilder.java +++ b/modules/kernel/src/org/apache/axis2/util/MessageContextBuilder.java @@ -114,6 +114,9 @@ public class MessageContextBuilder { newmsgCtx.setProperty(Constants.OUT_TRANSPORT_INFO, inMessageContext.getProperty(Constants.OUT_TRANSPORT_INFO)); + newmsgCtx.setProperty(Constants.HTTP_RESPONSE_STATE, + inMessageContext.getProperty(Constants.HTTP_RESPONSE_STATE)); + handleCorrelationID(inMessageContext,newmsgCtx); return newmsgCtx; } diff --git a/modules/transport/http/src/main/java/org/apache/axis2/transport/http/AbstractHTTPTransportSender.java b/modules/transport/http/src/main/java/org/apache/axis2/transport/http/AbstractHTTPTransportSender.java index 06c9bcc094..8d89e9d6dc 100644 --- a/modules/transport/http/src/main/java/org/apache/axis2/transport/http/AbstractHTTPTransportSender.java +++ b/modules/transport/http/src/main/java/org/apache/axis2/transport/http/AbstractHTTPTransportSender.java @@ -271,9 +271,11 @@ public abstract class AbstractHTTPTransportSender extends AbstractHandler implem servletBasedOutTransportInfo = (ServletBasedOutTransportInfo) transportInfo; - // if sending a fault, set HTTP status code to 500 + // if sending a fault, set HTTP status code (respecting user-set status codes) if (msgContext.isFault()) { - servletBasedOutTransportInfo.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + int faultStatus = resolveHttpStatusCode(msgContext, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + servletBasedOutTransportInfo.setStatus(faultStatus); } Object customHeaders = msgContext.getProperty(HTTPConstants.HTTP_HEADERS); @@ -299,6 +301,12 @@ public abstract class AbstractHTTPTransportSender extends AbstractHandler implem } } } else if (transportInfo instanceof AxisHttpResponse) { + if (msgContext.isFault()) { + int faultStatus = resolveHttpStatusCode(msgContext, + HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + ((AxisHttpResponse) transportInfo).setStatus(faultStatus); + } + Object customHeaders = msgContext.getProperty(HTTPConstants.HTTP_HEADERS); if (customHeaders != null) { if (customHeaders instanceof List) { @@ -360,6 +368,39 @@ public abstract class AbstractHTTPTransportSender extends AbstractHandler implem } } + /** + * Resolves the HTTP status code to use for a response. Checks the message context + * (and the inbound message context as a fallback) for a user-specified status code + * via {@link Constants#HTTP_RESPONSE_STATE}. If none is found, returns the default. + * + * @param msgContext the current (outbound) message context + * @param defaultStatus the default HTTP status code to use if no user override is found + * @return the resolved HTTP status code + */ + private static int resolveHttpStatusCode(MessageContext msgContext, int defaultStatus) { + // Check the outbound message context first + String statusStr = (String) msgContext.getProperty(Constants.HTTP_RESPONSE_STATE); + + // Fall back to the inbound message context (createFaultMessageContext may not + // have copied this property in older code paths) + if (statusStr == null) { + MessageContext inMsgCtx = + (MessageContext) msgContext.getProperty(MessageContext.IN_MESSAGE_CONTEXT); + if (inMsgCtx != null) { + statusStr = (String) inMsgCtx.getProperty(Constants.HTTP_RESPONSE_STATE); + } + } + + if (statusStr != null) { + try { + return Integer.parseInt(statusStr); + } catch (NumberFormatException e) { + log.error("Invalid HTTP status code value: " + statusStr, e); + } + } + return defaultStatus; + } + private void writeMessageWithCommons(MessageContext messageContext, EndpointReference toEPR, OMOutputFormat format) throws AxisFault { diff --git a/modules/transport/http/src/main/java/org/apache/axis2/transport/http/AxisServlet.java b/modules/transport/http/src/main/java/org/apache/axis2/transport/http/AxisServlet.java index 96f99098f5..8fc6ebef78 100644 --- a/modules/transport/http/src/main/java/org/apache/axis2/transport/http/AxisServlet.java +++ b/modules/transport/http/src/main/java/org/apache/axis2/transport/http/AxisServlet.java @@ -512,7 +512,12 @@ public class AxisServlet extends HttpServlet { if (valueElement != null) { if (SOAP12Constants.FAULT_CODE_SENDER.equals(valueElement.getTextAsQName().getLocalPart()) && !msgContext.isDoingREST()) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + // Only set SOAP 1.2 Sender → 400 if the user hasn't already set a custom status + if (msgContext.getProperty(Constants.HTTP_RESPONSE_STATE) == null) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + faultContext.setProperty(Constants.HTTP_RESPONSE_STATE, + String.valueOf(HttpServletResponse.SC_BAD_REQUEST)); + } } } } diff --git a/modules/transport/http/src/test/java/org/apache/axis2/transport/http/HTTPTransportSenderTest.java b/modules/transport/http/src/test/java/org/apache/axis2/transport/http/HTTPTransportSenderTest.java index a3353da4e8..173b1541e5 100644 --- a/modules/transport/http/src/test/java/org/apache/axis2/transport/http/HTTPTransportSenderTest.java +++ b/modules/transport/http/src/test/java/org/apache/axis2/transport/http/HTTPTransportSenderTest.java @@ -149,6 +149,68 @@ public abstract class HTTPTransportSenderTest extends TestCase { } + /** + * AXIS2-3879: Fault with a user-specified custom status code should use that code, + * not the default 500. + */ + public void testFaultWithCustomStatusCode() throws Exception { + MockHttpServletResponse httpResponse = new MockHttpServletResponse(); + ServletBasedOutTransportInfo info = new ServletBasedOutTransportInfo(httpResponse); + configAndRunFault(httpResponse, info, "503", getTransportSender()); + assertEquals("Custom status code should be respected", 503, httpResponse.getStatus()); + } + + /** + * AXIS2-3879: Fault without a custom status code should default to 500. + */ + public void testFaultDefaultsTo500() throws Exception { + MockHttpServletResponse httpResponse = new MockHttpServletResponse(); + ServletBasedOutTransportInfo info = new ServletBasedOutTransportInfo(httpResponse); + configAndRunFault(httpResponse, info, null, getTransportSender()); + assertEquals("Default fault status should be 500", 500, httpResponse.getStatus()); + } + + /** + * AXIS2-4146: User-set status code 400 should not be overwritten to 500. + */ + public void testFaultWithStatus400NotOverwritten() throws Exception { + MockHttpServletResponse httpResponse = new MockHttpServletResponse(); + ServletBasedOutTransportInfo info = new ServletBasedOutTransportInfo(httpResponse); + configAndRunFault(httpResponse, info, "400", getTransportSender()); + assertEquals("Status 400 should not be overwritten to 500", 400, httpResponse.getStatus()); + } + + private static void configAndRunFault(MockHttpServletResponse outResponse, + OutTransportInfo outTransportInfo, String customStatus, + TransportSender sender) throws Exception { + ConfigurationContext confContext = ConfigurationContextFactory + .createEmptyConfigurationContext(); + TransportOutDescription transportOut = new TransportOutDescription("http"); + Parameter param = new Parameter(HTTPConstants.OMIT_SOAP_12_ACTION, false); + SOAPEnvelope envelope = getFaultEnvelope(); + MessageContext msgContext = new MessageContext(); + + transportOut.addParameter(param); + msgContext.setEnvelope(envelope); + msgContext.setProperty(MessageContext.TRANSPORT_OUT, + outResponse.getByteArrayOutputStream()); + msgContext.setProperty(Constants.OUT_TRANSPORT_INFO, outTransportInfo); + msgContext.setTransportOut(transportOut); + msgContext.setConfigurationContext(confContext); + if (customStatus != null) { + msgContext.setProperty(Constants.HTTP_RESPONSE_STATE, customStatus); + } + sender.init(confContext, transportOut); + sender.invoke(msgContext); + } + + static SOAPEnvelope getFaultEnvelope() { + SOAPFactory soapFac = OMAbstractFactory.getSOAP11Factory(); + SOAPEnvelope envelope = soapFac.getDefaultFaultEnvelope(); + envelope.getBody().getFault().getReason().setText("test fault"); + return envelope; + } + static SOAPEnvelope getEnvelope() throws IOException { SOAPFactory soapFac = OMAbstractFactory.getSOAP11Factory(); OMFactory omFac = OMAbstractFactory.getOMFactory(); diff --git a/modules/transport/http/src/test/java/org/apache/axis2/transport/http/mock/MockHttpServletResponse.java b/modules/transport/http/src/test/java/org/apache/axis2/transport/http/mock/MockHttpServletResponse.java index a3c1e75375..cc5473ba2b 100644 --- a/modules/transport/http/src/test/java/org/apache/axis2/transport/http/mock/MockHttpServletResponse.java +++ b/modules/transport/http/src/test/java/org/apache/axis2/transport/http/mock/MockHttpServletResponse.java @@ -49,8 +49,9 @@ public class MockHttpServletResponse implements HttpServletResponse, OutTranspor private OutputStream outStream; private boolean committed; private HeaderGroup headerGroup; - private ByteArrayOutputStream byteArrayOutputStream; - + private ByteArrayOutputStream byteArrayOutputStream; + private int status = 200; + public MockHttpServletResponse() { headerGroup = new HeaderGroup(); byteArrayOutputStream = new ByteArrayOutputStream(); @@ -203,6 +204,7 @@ public class MockHttpServletResponse implements HttpServletResponse, OutTranspor @Override public void setStatus(int sc) { + this.status = sc; } @Override @@ -222,7 +224,7 @@ public class MockHttpServletResponse implements HttpServletResponse, OutTranspor @Override public int getStatus() { - throw new UnsupportedOperationException(); + return status; } @Override
