Spring Integration : Connection Failure vs Error response code retryadvice() - spring-integration-http

i'm sending a request to a web service through Http.outboundGateway, and i 'm expecting to have a response with on of these three cases
1- Response success [Ok]
2- Connection Failure [ need to Retry]
3- response return with error code, ex. 400 [save it]
i used advice() after poller to reattempt the Connection Failure, but The problem is that the Error Message exception was thrown in both cases ( Connection Failure , response error code ), so the retry was called for both cases
How could i differentiate between of them and only use the Retry advice for the Connection Failure
.handle(
Http.outboundGateway(propertiesConfig.getURL())
......
, endpoint -> endpoint
.poller(Pollers.fixedDelay(delayBetweenRequests)
.errorChannel("errorChannel")
.taskExecutor(executor)
.receiveTimeout(timeoutDelay)
)
.advice(retryAdvice)
)
Retry advice creation bean
#Bean("retryAdvice")
public RequestHandlerRetryAdvice maspRetryAdvice() {
Request
HandlerRetryAdvice retryAdvice = new RequestHandlerRetryAdvice();
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy policy = new FixedBackOffPolicy();
policy.setBackOffPeriod(interval);
retryTemplate.setBackOffPolicy(policy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(nRetry);
retryTemplate.setRetryPolicy(retryPolicy);
retryAdvice.setRetryTemplate(retryTemplate);
ErrorMessageSendingRecoverer recover = new
ErrorMessageSendingRecoverer(aggregatorChannel());
handlerRetryAdvice.setRecoveryCallback(recover);
return retryAdvice;
}

I was facing almost the exact same challenge.
For Connection time out this link provides the exact solution: [Configure error handling and retry for Http.outboundGateway spring dsl
] [1]: Configure error handling and retry for Http.outboundGateway spring dsl
....
.handle(Http.outboundGateway(
parser().parseExpression("headers[url]"))
.httpMethod(HttpMethod.POST)
.headerMapper(headerMapper())
.expectedResponseType(String.class)
.requestFactory(clientHttpRequestFactory())
// The inner writer method is doing nothing, just place holder for future usage,
// errorHandler is necessary to capture :
// e.g. org.springframework.web.client.HttpClientErrorException: 403 Forbidden
.errorHandler(responseErrorFileWriter())
, (Consumer<GenericEndpointSpec>)e -> e.advice(retryAdvice()))

Related

Apache Camel return producer errors

I have a simple proxy configuration like the following:
from("netty-http://0.0.0.0:8080/xyz")
.toD("netty-http:" + "https://abc/xyu")
.process(this::processResponse);
But when abc service return 400 with readable errors, camel return own exception:
org.apache.camel.component.netty.http.NettyHttpOperationFailedException: Netty HTTP operation failed invoking
What can I do to simply return the error from producer service?
You can try setting the throwExceptionOnFailure option to false. Http components in camel have this enabled on default, probably so that failures can easily be caught and handled like any other exceptions in camel.
When its enabled you can get the Exception and details about the exception through Exchange properties which in this chase you can probably cast to NettyHttpOperationFailedException to get more information about it.
Exception cause = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
I had a similar issue with jetty (Apache-Camel / Camel-Jetty / Don't return exception-stack-trace in case of 401 response)
In my case the options bridgeEndpoint=true and throwExceptionOnFailure=false solved the issue (except for http 401)
from("netty-http://0.0.0.0:8080/xyz")
.toD("netty-http:" + "https://abc/xyu?bridgeEndpoint=true&throwExceptionOnFailure=false")
.process(this::processResponse);

com.ctc.wstx.exc.WstxParsingException: Unexpected close tag </span>; expected </br>

I created the stub classes using CXF wsdl2java tool.
I am using Apache CXF library, with JCIFS. I validated the WSDL file itself through couple tools, it is good. Here is the code. It looks like some setting I must do.
//JCIFS Authentication related code
jcifs.Config.setProperty("jcifs.smb.client.domain", "NTS");
jcifs.Config.setProperty("jcifs.netbios.wins", "ecmchat.mark.gov");
jcifs.Config.setProperty("jcifs.smb.client.soTimeout", "300000"); // 5 minutes
jcifs.Config.setProperty("jcifs.netbios.cachePolicy", "1200"); // 20 minutes
jcifs.Config.setProperty("jcifs.smb.client.username", "user");
jcifs.Config.setProperty("jcifs.smb.client.password", "password");
//Register the jcifs URL handler to enable NTLM
jcifs.Config.registerSmbURLHandler();
//WSDL and Client settings
URL wsdlURL = BF.WSDL_LOCATION;
if (args.length > 0 && args[0] != null && !"".equals(args[0])) {
File wsdlFile = new File(args[0]);
try {
if (wsdlFile.exists()) {
wsdlURL = wsdlFile.toURI().toURL();
} else {
wsdlURL = new URL(args[0]);
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
BF ss = new BF(wsdlURL, SERVICE_NAME);
BFSoap port = ss.getBFSoap12();
Client client = ClientProxy.getClient(port);
HTTPConduit http = (HTTPConduit) client.getConduit();
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(36000);
httpClientPolicy.setAllowChunking(false);
httpClientPolicy.setReceiveTimeout(32000);
http.setClient(httpClientPolicy);
// Calling the method
System.out.println("Invoking testMethod...");
String _testMethod__return = port.testMethod();
System.out.println("testMethod.result=" + _testMethod__return);
I am getting the following exception
Caused by: com.ctc.wstx.exc.WstxParsingException: Unexpected close tag </span>; expected </br>.
at [row,col,system-id]: [59,22,"https://ecmchat.mark.gov/BF/BF.asmx"]
at com.ctc.wstx.sr.StreamScanner.constructWfcException(StreamScanner.java:621)
at com.ctc.wstx.sr.StreamScanner.throwParseError(StreamScanner.java:491)
at com.ctc.wstx.sr.StreamScanner.throwParseError(StreamScanner.java:475)
at com.ctc.wstx.sr.BasicStreamReader.reportWrongEndElem(BasicStreamReader.java:3365)
at com.ctc.wstx.sr.BasicStreamReader.readEndElem(BasicStreamReader.java:3292)
at com.ctc.wstx.sr.BasicStreamReader.nextFromTree(BasicStreamReader.java:2911)
at com.ctc.wstx.sr.BasicStreamReader.next(BasicStreamReader.java:1123)
at org.apache.cxf.staxutils.StaxUtils.readDocElements(StaxUtils.java:1361)
at org.apache.cxf.staxutils.StaxUtils.readDocElements(StaxUtils.java:1255)
at org.apache.cxf.staxutils.StaxUtils.read(StaxUtils.java:1183)
at org.apache.cxf.wsdl11.WSDLManagerImpl.loadDefinition(WSDLManagerImpl.java:235)
... 9 more
If I comment out the JCIFS NTLM authentication code, I get a HTTP 401 error. Therefore, I believe, at least it is passing some kind of authorization step.
And, if I use local WSDL in place of remote URL WSDL, then I get a different error like "method not implemented" on the call to the method. May be this is due to me not using the local WSDL correctly. I do not even know if we can use the local WSDL reference for remote service.
Then, I created a SoapUI dummy service with this WSDL, and the same code (but without the JCIFS authentication code) works good, and successfully calls the methods.
It appears to me that I must add some more appropriate settings in the configuration related code.
Am I right, and are you aware of any, for NTLM authentication and Apache CXF?
But parsing error is confusing???
I do not know if this is related.
My original WSDL URL that I gave was this.
https://ecmchat.mark.gov/BF/BF.asmx
I added a ?wsdl like below
https://ecmchat.mark.gov/BF/BF.asmx?wsdl
Then I am getting a different error.
I wonder why it is working if I access my local SoapUI version of the same WSDL service, but not for the remote one.
Invoking testMethod...
Jan 07, 2020 10:47:25 AM org.apache.cxf.phase.PhaseInterceptorChain doDefaultLogging
WARNING: Interceptor for {https://ecmchat.mark.gov}BF#{https://ecmchat.mark.gov}testMethod has thrown exception, unwinding now
java.lang.UnsupportedOperationException: Method not implemented.
at java.net.URLStreamHandler.openConnection(URLStreamHandler.java:96)
at java.net.URL.openConnection(URL.java:1028)
at org.apache.cxf.transport.https.HttpsURLConnectionFactory.createConnection(HttpsURLConnectionFactory.java:92)
at org.apache.cxf.transport.http.URLConnectionHTTPConduit.createConnection(URLConnectionHTTPConduit.java:121)
at org.apache.cxf.transport.http.URLConnectionHTTPConduit.setupConnection(URLConnectionHTTPConduit.java:125)
at org.apache.cxf.transport.http.HTTPConduit.prepare(HTTPConduit.java:505)
at org.apache.cxf.interceptor.MessageSenderInterceptor.handleMessage(MessageSenderInterceptor.java:47)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308)
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:530)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:441)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:356)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:314)
at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:96)
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:140)
at com.sun.proxy.$Proxy33.testMethod(Unknown Source)
at edison.learn.BFSoap_BFSoap12_Client.main(BFSoap_BFSoap12_Client.java:90)
Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: Method not implemented.
at org.apache.cxf.jaxws.JaxWsClientProxy.mapException(JaxWsClientProxy.java:195)
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:145)
at com.sun.proxy.$Proxy33.testMethod(Unknown Source)
at edison.learn.BFSoap_BFSoap12_Client.main(BFSoap_BFSoap12_Client.java:90)
Caused by: java.lang.UnsupportedOperationException: Method not implemented.
at java.net.URLStreamHandler.openConnection(URLStreamHandler.java:96)
at java.net.URL.openConnection(URL.java:1028)
at org.apache.cxf.transport.https.HttpsURLConnectionFactory.createConnection(HttpsURLConnectionFactory.java:92)
at org.apache.cxf.transport.http.URLConnectionHTTPConduit.createConnection(URLConnectionHTTPConduit.java:121)
at org.apache.cxf.transport.http.URLConnectionHTTPConduit.setupConnection(URLConnectionHTTPConduit.java:125)
at org.apache.cxf.transport.http.HTTPConduit.prepare(HTTPConduit.java:505)
at org.apache.cxf.interceptor.MessageSenderInterceptor.handleMessage(MessageSenderInterceptor.java:47)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308)
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:530)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:441)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:356)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:314)
at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:96)
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:140)
... 2 more

Apache Camel: Unable to get the Exception Body

Whenever there is normal flow in my Camel Routes I am able to get the body in the next component. But whenever there is an exception(Http 401 or 500) I am unable to get the exception body. I just get a java exception in my server logs.
I have also tried onException().. Using that the flow goes into it on error, but still I do not get the error response body that was sent by the web service(which I get when using POSTMAN directly), I only get the request in the body that I had sent to the web service.
Also adding the route:
from("direct:contractUpdateAds")
.to("log:inside_direct:contractUpdateAds_route_CompleteLog?level=INFO&showAll=true&multiline=true")
.streamCaching()
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.log("before calling ADS for ContractUpdate:\nBody:${body}")
.to("{{AdsContractUpdateEndpoint}}")
.log("after calling ADS for ContractUpdate:\nBody:${body}")
.convertBodyTo(String.class)
.end();
Option 1: handle failure status codes yourself
The throwExceptionOnFailure=false endpoint option (available at least for camel-http and camel-http4 endpoints) is probably what you want. With this option, camel-http will no longer consider an HTTP Status >= 300 as an error, and will let you decide what to do - including processing the response body however you see fit.
Something along those lines should work :
from("...")
.to("http://{{hostName}}?throwExceptionOnFailure=false")
.choice()
.when(header(Exchange.HTTP_RESPONSE_CODE).isLessThan(300))
// HTTP status < 300
.to("...")
.otherwise()
// HTTP status >= 300 : would throw an exception if we had "throwExceptionOnFailure=true"
.log("Error response: ${body}")
.to("...");
This is an interesting approach if you want to have special handling for certains status codes for example. Note that the logic can be reused in several routes by using direct endpoints, just like any other piece of Camel route logic.
Option 2 : Access the HttpOperationFailedException in the onException
If you want to keep the default error handling, but you want to access the response body in the exception handling code for some reason, you just need to access the responseBody property on the HttpOperationFailedException.
Here's an example:
onException(HttpOperationFailedException.class)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
// e won't be null because we only catch HttpOperationFailedException;
// otherwise, we'd need to check for null.
final HttpOperationFailedException e =
exchange.getProperty(Exchange.EXCEPTION_CAUGHT, HttpOperationFailedException.class);
// Do something with the responseBody
final String responseBody = e.getResponseBody();
}
});

camel with netty4-http - EncoderException

I'm using camel 2.14.0 with netty4-http
and I get the following exception.
the scenario is this:
I have a route that sends a request, waits for the response (inOut) and then sends another request.
the first request works, and then the second one fails.
also, if I do it quickly enough after the failure - the first request will also fail.
while debugging a bit (HttpObjectEncoder) - I saw that in the working flow the state of the request is: state = ST_INIT (0)
and in the request that failed it is: ST_CONTENT_NON_CHUNK (1)
which causes the illegal state when the type of message is HttpMessage
is this a bug or is there anything I can configure to fix it?
Caused by: io.netty.handler.codec.EncoderException: java.lang.IllegalStateException: unexpected message type: DefaultFullHttpRequest
at io.netty.handler.codec.MessageToMessageEncoder.write(MessageToMessageEncoder.java:107)
at io.netty.channel.CombinedChannelDuplexHandler.write(CombinedChannelDuplexHandler.java:192)
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:658)
at io.netty.channel.AbstractChannelHandlerContext.access$2000(AbstractChannelHandlerContext.java:32)
at io.netty.channel.AbstractChannelHandlerContext$AbstractWriteTask.write(AbstractChannelHandlerContext.java:939)
at io.netty.channel.AbstractChannelHandlerContext$WriteAndFlushTask.write(AbstractChannelHandlerContext.java:991)
at io.netty.channel.AbstractChannelHandlerContext$AbstractWriteTask.run(AbstractChannelHandlerContext.java:924)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:380)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:357)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalStateException: unexpected message type: DefaultFullHttpRequest
at io.netty.handler.codec.http.HttpObjectEncoder.encode(HttpObjectEncoder.java:63)
at io.netty.handler.codec.http.HttpClientCodec$Encoder.encode(HttpClientCodec.java:106)
at io.netty.handler.codec.MessageToMessageEncoder.write(MessageToMessageEncoder.java:89)
... 10 more
I managed to identify the problem:
the first request I sent was a GET request with null body.
in the class org.apache.camel.component.netty4.http.NettyHttpProducer -
the method getRequestBody(Exchange exchange) is creating the actual request object from the exchange.
in it - the method "toNettyRequest" in class org.apache.camel.component.netty4.http.DefaultNettyHttpBinding
checks if the body is null, and if so - it is creating a DefaultHttpRequest, and not DefaultHttpFullRequest
when the request reaches the encoder as a result of a writeAndFlush call - the encoder does not clean its state because of this part of the code:
if (msg instanceof LastHttpContent) {
state = ST_INIT;
}
the DefaultHttpRequest is not instanceof LastHttpContent, so the state remains ST_CONTENT_NON_CHUNK and the next request will get an IllegalStateException because the state is not ST_INIT
this bug did not exist in netty-http, it only happened when I moved to use netty4-http
the workaround is simple - use an empty String ("") as body

CXF - catching/handling HTTP exceptions

This is rather a basic cxf usage question. How/where can we catch the actual HTTP exception/error. I kind of followed the Interceptor/MessageObserver concept but could not capture the HTTP error using them.
I see this error in the log4j log file.
Caused by: org.apache.cxf.transport.http.HTTPException: HTTP response
'401: Unauthorized' when communicating with http://10.107.172.79/test/_vti_bin/lists.asmx
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1502)
at org.apache.cxf.transpot.http.HTTPConduit$WrappedOutputStream.handleResponse(HTTPConduit.java:1448)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1356)
at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56)
at org.apache.cxf.transport.http.HTTPConduit.close(HTTPConduit.java:614)
at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:62)
... 9 more
Only javax.xml.ws.WebServiceException with "Could not send Message."
message is thrown while calling the service
try{
GetListCollectionResult result = port.getListCollection();
}catch (javax.xml.ws.WebServiceException excep){
}
This is how we call the service.
To provide NTLM credentials:
Authenticator.setDefault( extended class of Authenticator);
Create the service.
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(ListsSoap.class);
factory.setAddress(list_url);
ListsSoap port = (ListsSoap) factory.create();
Update the conduit.
..
Client client = ClientProxy.getClient(port);
HTTPConduit http = (HTTPConduit) client.getConduit();
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(36000);
httpClientPolicy.setAllowChunking(false);
http.setClient(httpClientPolicy);
Call service and get the result.
GetListCollectionResult result = port.getListCollection();
Nevermind, I found the answer in the CXF mailing list.
excep.getCause()
gives access to the underlying exception, in my case it is the HTTP Transport exception.

Resources