2222
2323import org .apache .axiom .mime .ContentType ;
2424import org .apache .axiom .mime .Header ;
25+ import org .apache .axiom .om .OMAbstractFactory ;
2526import org .apache .axiom .om .OMAttribute ;
2627import org .apache .axiom .om .OMElement ;
2728import org .apache .axiom .om .OMOutputFormat ;
4041import org .apache .commons .logging .Log ;
4142import org .apache .commons .logging .LogFactory ;
4243
44+ import org .apache .hc .core5 .http .HttpEntity ;
4345import org .apache .hc .core5 .http .HttpStatus ;
4446import org .apache .hc .core5 .http .HttpHeaders ;
4547
48+ import java .io .BufferedReader ;
4649import java .io .IOException ;
4750import java .io .InputStream ;
51+ import java .io .InputStreamReader ;
4852import java .net .URL ;
4953import java .text .ParseException ;
5054import java .util .HashMap ;
5155import java .util .Iterator ;
5256import java .util .List ;
5357import java .util .Map ;
58+ import java .util .Optional ;
5459import java .util .Set ;
60+ import java .util .stream .Collectors ;
5561import java .util .zip .GZIPInputStream ;
5662
5763import 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.
6078public 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 " , " " );
664+ return "<![CDATA[" + safeContent + "]]>" ;
665+
666+ }
667+
501668}
0 commit comments