Skip to content

Commit c54cc29

Browse files
Merge pull request #902 from JWT007/bugfix/AXIS2-6091
AXIS2-6091 - Handle 400-500 errors with content-type "text/html"
2 parents db14a9e + 629a86f commit c54cc29

File tree

2 files changed

+224
-7
lines changed

2 files changed

+224
-7
lines changed

modules/kernel/src/org/apache/axis2/kernel/http/HTTPConstants.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package org.apache.axis2.kernel.http;
2222

2323
import java.io.UnsupportedEncodingException;
24+
import javax.xml.namespace.QName;
2425

2526
/**
2627
* HTTP protocol and message context constants.
@@ -533,4 +534,53 @@ public static byte[] getBytes(final String data) {
533534

534535
public static final String USER_AGENT = "userAgent";
535536
public static final String SERVER = "server";
537+
538+
/** Base QName namespace for HTTP errors. */
539+
public static final String QNAME_HTTP_NS =
540+
"http://ws.apache.org/axis2/http";
541+
542+
/** QName for faults caused by a 400 Bad Request HTTP response. */
543+
public static final QName QNAME_HTTP_BAD_REQUEST =
544+
new QName(QNAME_HTTP_NS, "BAD_REQUEST");
545+
546+
/** QName for faults caused by a 401 Unauthorized HTTP response. */
547+
public static final QName QNAME_HTTP_UNAUTHORIZED =
548+
new QName(QNAME_HTTP_NS, "UNAUTHORIZED");
549+
550+
/** QName for faults caused by a 403 Forbidden HTTP response. */
551+
public static final QName QNAME_HTTP_FORBIDDEN =
552+
new QName(QNAME_HTTP_NS, "FORBIDDEN");
553+
554+
/** QName for faults caused by a 404 Not Found HTTP response. */
555+
public static final QName QNAME_HTTP_NOT_FOUND =
556+
new QName(QNAME_HTTP_NS, "NOT_FOUND");
557+
558+
/** QName for faults caused by a 405 Method Not Allowed HTTP response. */
559+
public static final QName QNAME_HTTP_METHOD_NOT_ALLOWED =
560+
new QName(QNAME_HTTP_NS, "METHOD_NOT_ALLOWED");
561+
562+
/** QName for faults caused by a 406 Not Acceptable HTTP response. */
563+
public static final QName QNAME_HTTP_NOT_ACCEPTABLE =
564+
new QName(QNAME_HTTP_NS, "NOT_ACCEPTABLE");
565+
566+
/** QName for faults caused by a 407 Proxy Authentication Required HTTP response. */
567+
public static final QName QNAME_HTTP_PROXY_AUTH_REQUIRED =
568+
new QName(QNAME_HTTP_NS, "PROXY_AUTHENTICATION_REQUIRED");
569+
570+
/** QName for faults caused by a 408 Request Timeout HTTP response. */
571+
public static final QName QNAME_HTTP_REQUEST_TIMEOUT =
572+
new QName(QNAME_HTTP_NS, "REQUEST_TIMEOUT");
573+
574+
/** QName for faults caused by a 409 Conflict HTTP response. */
575+
public static final QName QNAME_HTTP_CONFLICT =
576+
new QName(QNAME_HTTP_NS, "CONFLICT");
577+
578+
/** QName for faults caused by a 410 Gone HTTP response. */
579+
public static final QName QNAME_HTTP_GONE =
580+
new QName(QNAME_HTTP_NS, "GONE");
581+
582+
/** QName for faults caused by a 500 Internal Server Error HTTP response. */
583+
public static final QName QNAME_HTTP_INTERNAL_SERVER_ERROR =
584+
new QName(QNAME_HTTP_NS, "INTERNAL_SERVER_ERROR");
585+
536586
}

modules/transport/http/src/main/java/org/apache/axis2/transport/http/HTTPSender.java

Lines changed: 174 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.apache.axiom.mime.ContentType;
2424
import org.apache.axiom.mime.Header;
25+
import org.apache.axiom.om.OMAbstractFactory;
2526
import org.apache.axiom.om.OMAttribute;
2627
import org.apache.axiom.om.OMElement;
2728
import org.apache.axiom.om.OMOutputFormat;
@@ -40,22 +41,39 @@
4041
import org.apache.commons.logging.Log;
4142
import org.apache.commons.logging.LogFactory;
4243

44+
import org.apache.hc.core5.http.HttpEntity;
4345
import org.apache.hc.core5.http.HttpStatus;
4446
import org.apache.hc.core5.http.HttpHeaders;
4547

48+
import java.io.BufferedReader;
4649
import java.io.IOException;
4750
import java.io.InputStream;
51+
import java.io.InputStreamReader;
4852
import java.net.URL;
4953
import java.text.ParseException;
5054
import java.util.HashMap;
5155
import java.util.Iterator;
5256
import java.util.List;
5357
import java.util.Map;
58+
import java.util.Optional;
5459
import java.util.Set;
60+
import java.util.stream.Collectors;
5561
import java.util.zip.GZIPInputStream;
5662

5763
import javax.xml.namespace.QName;
5864

65+
import static org.apache.axis2.kernel.http.HTTPConstants.QNAME_HTTP_BAD_REQUEST;
66+
import static org.apache.axis2.kernel.http.HTTPConstants.QNAME_HTTP_CONFLICT;
67+
import static org.apache.axis2.kernel.http.HTTPConstants.QNAME_HTTP_FORBIDDEN;
68+
import static org.apache.axis2.kernel.http.HTTPConstants.QNAME_HTTP_GONE;
69+
import static org.apache.axis2.kernel.http.HTTPConstants.QNAME_HTTP_INTERNAL_SERVER_ERROR;
70+
import static org.apache.axis2.kernel.http.HTTPConstants.QNAME_HTTP_METHOD_NOT_ALLOWED;
71+
import static org.apache.axis2.kernel.http.HTTPConstants.QNAME_HTTP_NOT_ACCEPTABLE;
72+
import static org.apache.axis2.kernel.http.HTTPConstants.QNAME_HTTP_NOT_FOUND;
73+
import static org.apache.axis2.kernel.http.HTTPConstants.QNAME_HTTP_PROXY_AUTH_REQUIRED;
74+
import static org.apache.axis2.kernel.http.HTTPConstants.QNAME_HTTP_REQUEST_TIMEOUT;
75+
import static org.apache.axis2.kernel.http.HTTPConstants.QNAME_HTTP_UNAUTHORIZED;
76+
5977
//TODO - It better if we can define these method in a interface move these into AbstractHTTPSender and get rid of this class.
6078
public abstract class HTTPSender {
6179

@@ -196,7 +214,9 @@ public void send(MessageContext msgContext, URL url, String soapActionString)
196214
boolean cleanup = true;
197215
try {
198216
int statusCode = request.getStatusCode();
199-
log.trace("Handling response - " + statusCode);
217+
218+
log.trace("Handling response - [content-type='" + contentType + "', statusCode=" + statusCode + "]");
219+
200220
boolean processResponse;
201221
boolean fault;
202222
if (statusCode == HttpStatus.SC_ACCEPTED) {
@@ -205,14 +225,22 @@ public void send(MessageContext msgContext, URL url, String soapActionString)
205225
} else if (statusCode >= 200 && statusCode < 300) {
206226
processResponse = true;
207227
fault = false;
208-
} else if (statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
209-
|| statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_NOT_FOUND) {
210-
processResponse = true;
211-
fault = true;
228+
} else if (statusCode >= 400 && statusCode <= 500) {
229+
230+
// if the response has a HTTP error code (401/404/500) but is *not* a SOAP response, handle it here
231+
if (contentType != null && contentType.startsWith("text/html")) {
232+
throw handleNonSoapError(request, statusCode);
233+
} else {
234+
processResponse = true;
235+
fault = true;
236+
}
237+
212238
} else {
213-
throw new AxisFault(Messages.getMessage("transportError", String.valueOf(statusCode),
239+
throw new AxisFault(Messages.getMessage("transportError",
240+
String.valueOf(statusCode),
214241
request.getStatusText()));
215242
}
243+
216244
obtainHTTPHeaderInformation(request, msgContext);
217245
if (processResponse) {
218246
OperationContext opContext = msgContext.getOperationContext();
@@ -266,7 +294,7 @@ public void send(MessageContext msgContext, URL url, String soapActionString)
266294
log.info("Unable to send to url[" + url + "]", e);
267295
throw AxisFault.makeFault(e);
268296
}
269-
}
297+
}
270298

271299
private void addCustomHeaders(MessageContext msgContext, Request request) {
272300

@@ -498,4 +526,143 @@ private String buildCookieString(Map<String,String> cookies, String name) {
498526
String value = cookies.get(name);
499527
return value == null ? null : name + "=" + value;
500528
}
529+
530+
/**
531+
* Handles non-SOAP HTTP error responses (e.g., 404, 500) by creating an AxisFault.
532+
* <p>
533+
* If the response is `text/html`, it extracts the response body and includes it
534+
* as fault details, wrapped within a CDATA block.
535+
* </p>
536+
*
537+
* @param request the HTTP request instance
538+
* @param statusCode the HTTP status code
539+
* @return AxisFault containing the error details
540+
*/
541+
private AxisFault handleNonSoapError(final Request request, final int statusCode) {
542+
543+
String responseContent = null;
544+
545+
InputStream responseContentInputStream = null;
546+
try {
547+
responseContentInputStream = request.getResponseContent();
548+
} catch (final IOException ex) {
549+
// NO-OP
550+
}
551+
552+
if (responseContentInputStream != null) {
553+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(responseContentInputStream))) {
554+
responseContent = reader.lines().collect(Collectors.joining("\n")).trim();
555+
} catch (IOException e) {
556+
log.warn("Failed to read response content from HTTP error response", e);
557+
}
558+
}
559+
560+
// Build and throw an AxisFault with the response content
561+
final String faultMessage =
562+
Messages.getMessage("transportError", String.valueOf(statusCode), responseContent);
563+
564+
final QName faultQName = getFaultQNameForStatusCode(statusCode).orElse(null);
565+
566+
final AxisFault fault = new AxisFault(faultMessage, faultQName);
567+
final OMElement faultDetail = createFaultDetailForNonSoapError(responseContent);
568+
fault.setDetail(faultDetail);
569+
570+
return fault;
571+
572+
}
573+
574+
/**
575+
* Returns an appropriate QName for the given HTTP status code.
576+
*
577+
* @param statusCode the HTTP status code (e.g., 404, 500)
578+
* @return an Optional containing the QName if available, or an empty Optional if the status code is unsupported
579+
*/
580+
private Optional<QName> getFaultQNameForStatusCode(int statusCode) {
581+
582+
final QName faultQName;
583+
584+
switch (statusCode) {
585+
case HttpStatus.SC_BAD_REQUEST:
586+
faultQName = QNAME_HTTP_BAD_REQUEST;
587+
break;
588+
case HttpStatus.SC_UNAUTHORIZED:
589+
faultQName = QNAME_HTTP_UNAUTHORIZED;
590+
break;
591+
case HttpStatus.SC_FORBIDDEN:
592+
faultQName = QNAME_HTTP_FORBIDDEN;
593+
break;
594+
case HttpStatus.SC_NOT_FOUND:
595+
faultQName = QNAME_HTTP_NOT_FOUND;
596+
break;
597+
case HttpStatus.SC_METHOD_NOT_ALLOWED:
598+
faultQName = QNAME_HTTP_METHOD_NOT_ALLOWED;
599+
break;
600+
case HttpStatus.SC_NOT_ACCEPTABLE:
601+
faultQName = QNAME_HTTP_NOT_ACCEPTABLE;
602+
break;
603+
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
604+
faultQName = QNAME_HTTP_PROXY_AUTH_REQUIRED;
605+
break;
606+
case HttpStatus.SC_REQUEST_TIMEOUT:
607+
faultQName = QNAME_HTTP_REQUEST_TIMEOUT;
608+
break;
609+
case HttpStatus.SC_CONFLICT:
610+
faultQName = QNAME_HTTP_CONFLICT;
611+
break;
612+
case HttpStatus.SC_GONE:
613+
faultQName = QNAME_HTTP_GONE;
614+
break;
615+
case HttpStatus.SC_INTERNAL_SERVER_ERROR:
616+
faultQName = QNAME_HTTP_INTERNAL_SERVER_ERROR;
617+
break;
618+
default:
619+
faultQName = null;
620+
break;
621+
}
622+
623+
return Optional.ofNullable(faultQName);
624+
625+
}
626+
627+
/**
628+
* Creates a fault detail element containing the response content.
629+
*/
630+
private OMElement createFaultDetailForNonSoapError(String responseContent) {
631+
632+
final OMElement faultDetail =
633+
OMAbstractFactory.getOMFactory().createOMElement(new QName("http://ws.apache.org/axis2", "Details"));
634+
635+
final OMElement textNode =
636+
OMAbstractFactory.getOMFactory().createOMElement(new QName("http://ws.apache.org/axis2", "Text"));
637+
638+
if (responseContent != null && !responseContent.isEmpty()) {
639+
textNode.setText(wrapResponseWithCDATA(responseContent));
640+
} else {
641+
textNode.setText(wrapResponseWithCDATA("The endpoint returned no response content."));
642+
}
643+
644+
faultDetail.addChild(textNode);
645+
646+
return faultDetail;
647+
648+
}
649+
650+
/**
651+
* Wraps the given HTML response content in a CDATA block to allow it to be added as Text in a fault-detail.
652+
*
653+
* @param responseContent the response content
654+
* @return the CDATA-wrapped response
655+
*/
656+
private String wrapResponseWithCDATA(final String responseContent) {
657+
658+
if (responseContent == null || responseContent.isEmpty()) {
659+
return "<![CDATA[The endpoint returned no response content.]]>";
660+
}
661+
662+
// Replace closing CDATA sequences properly
663+
String safeContent = responseContent.replace("]]>", "]]]]><![CDATA[>").replace("\n", "&#10;");
664+
return "<![CDATA[" + safeContent + "]]>";
665+
666+
}
667+
501668
}

0 commit comments

Comments
 (0)