Spring Integration Java DSL and Http.outboundGateway: How to get the real error message JSON - spring-integration

How to get the real error message JSON when the Http.outboundGateway call is failed.
For example my program does the HTTP POST. The operation fails with the error code 400 Bad Request and the real error message is (tested with the Postman):
{
"name": [
"This field is needed."
]
}
I have the error channel like this:
#Bean
private IntegrationFlow myErrorChannel() {
return f -> f.handle("myErrorHandler", "handle")
....
;
}
and the Class MyErrorHandler is like this:
#Service
public class MyErrorHandler {
#ServiceActivator
public Message<MessageHandlingException> handle(Message<MessageHandlingException> message) {
...
}
}
Does the MessageHandlingException contain the real error message?
{
"name": [
"This field is needed."
]
}
I debugged the code and checked the MessageHandlingException exception and it seems it doesn't contain the real error message. The detailMessage contains the text 400 Bad Request, but I want to know the real error message.
How to get the real error message?
Edit:
This is working (I'm assigning the real error message to the new payload):
final RestClientResponseException clientException = (RestClientResponseException) messagingHandlingException.getCause();
payload = clientException.getResponseBodyAsString();

The Resttemplate uses a DefaultResponseErrorHandler by default. That one has a logic like:
protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
String statusText = response.getStatusText();
HttpHeaders headers = response.getHeaders();
byte[] body = getResponseBody(response);
Charset charset = getCharset(response);
switch (statusCode.series()) {
case CLIENT_ERROR:
throw HttpClientErrorException.create(statusCode, statusText, headers, body, charset);
case SERVER_ERROR:
throw HttpServerErrorException.create(statusCode, statusText, headers, body, charset);
default:
throw new UnknownHttpStatusCodeException(statusCode.value(), statusText, headers, body, charset);
}
}
An exception from here is thrown to the HttpRequestExecutingMessageHandler which really wraps it into the MessageHandlingException.
Since you say that you can handle the last one via your MyErrorHandler, I would suggest you just take a look into the cause of the MessageHandlingException, and you'll that RestClientResponseException with all the required info from the response.

Related

Spring Integration DSL - OAuth2ErrorHandler issues with 4XX series error codes

We are using spring integration DSL to call downstream services. But we are facing issues when 4XX series error code is returned by downstream service.
Below is the code snippet that we are using to call downstream services
#Bean
public IntegrationFlow getDataChannelFlow() {
return IntegrationFlows.from(MessageChannels.executor("getDataChannel", Executors.newCachedThreadPool()))
.enrichHeaders(h -> h.headerExpression(USER_REF, SRC_USER_REF)
.handle(Http.outboundGateway(endPoint1, auth2RestTemplate)
.uriVariable("data", PAYLOAD)
.httpMethod(HttpMethod.GET)
.transferCookies(true)
.expectedResponseType(String.class), e->e.advice(integrationAdvice).id("docIDAdvice"))
.transform(this::responseTransformer)
.enrichHeaders(header -> header.headerExpression("DOCUMENTS", PAYLOAD))
}
In case of 200 and 500 response from downstream services, our code is working fine but when we get 4XX series errors we are getting below exception in logs and control does not return back to transformer method
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8080/fetchUser": Attempted read from closed stream.; nested exception is java.io.IOException: Attempted read from closed stream.
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:785)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:138)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:732)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:612)
at org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler.exchange(HttpRequestExecutingMessageHandler.java:196)
... 42 more
Caused by: java.io.IOException: Attempted read from closed stream.
at org.apache.http.impl.io.ChunkedInputStream.read(ChunkedInputStream.java:141)
at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:118)
Few things that we noticed while debugging -
Spring's OAuth2ErrorHandler.java class differentiates between 4XX and 5XX series of errors
public boolean hasError(ClientHttpResponse response) throws IOException
{
return HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series())
|| this.errorHandler.hasError(response);
}
In above code snippet hasError() method returns true for 4XX series of codes and due to this we are getting IOException when below code snippet is executed
protected <T> T doExecute(URI url, #Nullable HttpMethod method, #Nullable RequestCallback requestCallback,
#Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
}
finally {
if (response != null) {
response.close();
}
}
}
Our expectation is that control should return back to transformer method so that we will have the control over response processing.
Any suggestions on this issue would be much appreciated.

Error handling - no output-channel or replyChannel header available

I am trying to handle exceptions using ExpressionEvaluatingRequestHandlerAdvice, have a transformer for a fail channel,
<int:transformer input-channel="afterFailureChannel" output-channel="validateOutputChannel" ref="testExceptionTransformer" method="handleLockServiceResponse"/>
In testExceptionTransformer, I am forming user defined exception and sending it in http response entity which I want to send as a rest api response, Even though transformer has outputChannel, application throws
org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
at org.springframework.integration.handler.AbstractMessageProducingHandler.sendOutput(AbstractMessageProducingHandler.java:452) ~[spring-integration-core-5.5.13.jar:5.5.13]
Could you please help?
Edit:
Transformer looks like this,
public ResponseEntity<Object> handleLockServiceResponse(Message<MessagingException> message) throws Exception {
ResponseEntity<Object> response = null;
LOGGER.error(message.getPayload().getFailedMessage().toString());
LOGGER.error(message.getPayload().getCause().toString());
try {
Throwable exception = message.getPayload().getCause();
if (exception.getCause() instanceof HttpClientErrorException) {
throw new handleValidationException(exception.getCause().getMessage());
}
}catch(handleValidationException ex){
return adapterErrorHandler.handleCustomValidationException(ex);
}
return response;
}
It indeed doesn't fail in your transformer since you have that output-channel it fails in the initial gateway when it tries to correlate the reply message into a TemporaryReplyChannel from headers. We need to see what your transformer does, but the rule of thumb is if you return a Message from the transformer, you have to coyp headers from request message. However with an ExpressionEvaluatingRequestHandlerAdvice and its failureChannel it is a bit tricky.
The logic there is like this:
if (evalResult != null && this.failureChannel != null) {
MessagingException messagingException =
new MessageHandlingExpressionEvaluatingAdviceException(message, "Handler Failed",
unwrapThrowableIfNecessary(exception), evalResult);
ErrorMessage errorMessage = new ErrorMessage(messagingException);
this.messagingTemplate.send(this.failureChannel, errorMessage);
}
It becomes obvious that ErrorMessage doesn't have a request message headers. So, you need to extract them from that exception via getFailedMessage() and that's the one is sent to your service instrumented with that ExpressionEvaluatingRequestHandlerAdvice.
We probably need to improve the doc on the matter: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#expression-advice
UPDATE
So, now you return a ResponseEntity from your transformer method and headers for the reply message is copied from that ErrorMessage we send from the ExpressionEvaluatingRequestHandlerAdvice. To preserve original message headers in the reply message you must do something like this:
public Message<ResponseEntity<Object>> handleLockServiceResponse(Message<MessagingException> message) throws Exception {
ResponseEntity<Object> response = null;
LOGGER.error(message.getPayload().getFailedMessage().toString());
LOGGER.error(message.getPayload().getCause().toString());
try {
Throwable exception = message.getPayload().getCause();
if (exception.getCause() instanceof HttpClientErrorException) {
throw new handleValidationException(exception.getCause().getMessage());
}
}catch(handleValidationException ex){
response = adapterErrorHandler.handleCustomValidationException(ex);
}
return MessageBuilder.withPayload(response).copyHeaders(message.getPayload().getFailedMessage().getHeaders()).build();
}

How to handle multiple possible response types with Retrofit2, Gson and Rx

The API i have to use sucks, and always returns HTTP 200. But sometimes there is proper response:
[{"blah": "blah"}, {"blah": "blah"}]
and sometimes, there is error:
{"error": "Something went wrong", "code": 123}
I'm using Retrofit2 with Gson converter and Rx adapter:
final Api api = new Retrofit.Builder()
.baseUrl(URL)
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(Api.class);
And now, when I receive error response, the onError handler is called with following exception:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:350)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:80)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:211)
at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.Observable.unsafeSubscribe(Observable.java:10144)
at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:230)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
How can I solve it? If I could get the response in the onError handler, I could reparse it with proper error model class. But it seems I can't get the raw response.
You can use a custom Gson deserializer to marshal both responses into a single object type. Here is a rough sketch of the idea assuming your current response type is List<Map<String, String>>, you will need to adjust based on your actual return type. I am also making the assumption that the API always returns an array on success --
public class MyResponse {
String error;
Integer code;
List<Map<String, String>> response;
}
interface MyApi {
#GET("/")
Observable<MyResponse> myCall();
}
private class MyResponseDeserializer implements JsonDeserializer<MyResponse> {
public MyResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
MyResponse response = new MyResponse();
if (json.isJsonArray()) {
// It is an array, parse the data
Type responseType = new TypeToken<List<Map<String, String>>>(){}.getType();
response.response = context.deserialize(json, responseType);
} else {
// Not an array, parse out the error info
JsonObject object = json.getAsJsonObject();
response.code = object.getAsJsonPrimitive("code").getAsInt();
response.error = object.getAsJsonPrimitive("error").getAsString();
}
return response;
}
}
Use the above to create a custom Gson
Gson gson = new GsonBuilder()
.registerTypeAdapter(MyResponse.class, new MyResponseDeserializer())
.create();
use that in your retrofit builder --
.addConverterFactory(GsonConverterFactory.create(gson))
You should also update your interface to return Observable<MyResponse>. You will get both success and error in onNext now. You'll need to inspect the object to determine if it is a successful response (response != null) or not.

Spring integration, Setting error-handler on http outbound gateway

If I add a custom error-handler to an int-http:outbound-gateway, the response body is not unmarshalled according to the expected-response-type, instead I only get a ResponseEntity returned. My custom error handler is pretty simple:
public class MyResponseErrorHandler extends DefaultResponseErrorHandler {
private static final Logger log = LoggerFactory.getLogger(AlmaGetUserResponseErrorHandler.class);
#Override
public boolean hasError(final ClientHttpResponse response) throws IOException {
// stop http 400 from returning true to error here.
log.debug("Request has returned error code {}", response.getStatusCode());
if (response.getBody() != null) {
String returnBody = IOUtils.toString(response.getBody(), "UTF-8");
log.debug("Checking error from response, code {}, body {}", response.getStatusCode(), returnBody);
}
return false;
}
}
As soon as I remove the error-handler, it unmarshalls the XML response into my POJO correctly.
The issue above was that the MyResponseErrorHandler class was streaming out the body content before it was being passed to the marshaller for expected-response-type. Hence the body was null and a plain ResponseEntity was returned.

Inconsistent ServiceStack exception handling

I have a simple service built with ServiceStack
public class GetContactMasterDataService : IService<GetContactMasterData>
{
public object Execute(GetContactMasterData getContactMasterData)
{
return ContactApi.FetchContactMasterData();
}
}
In a different namespace:
public class GetContactMasterData
{
}
public class GetContactMasterDataResponse
{
public ResponseStatus ResponseStatus { get; set; }
}
public static GetContactMasterDataResponse FetchContactMasterData()
{
throw new ApplicationException("CRASH");
}
When I send a JSON request I correctly get:
{
"ResponseStatus":{
"ErrorCode":"ApplicationException",
"Message":"CRASH",
}
}
When I send a soap12 request with soapUI, I get the typical yellow screen of death
<html>
<head>
<title>CRASH</title>
...
<h2> <i>CRASH</i> </h2></span>
<b> Description: </b>An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
...
<b> Exception Details: </b>System.ApplicationException: CRASH<br><br>
Is this the expected behavior? How can I get a neatly serialized ResponseStatus similar to the JSON response.
Thanks in advance.
The HTML error page you get doesn't looks like it's coming from ServiceStack, check to see if your website has something that could be hijacking the errors with its own page, e.g: <customErrors />.
The correct behavior for SOAP endpoints is to throw a SOAP fault which if you're using either the Soap11ServiceClient or Soap12ServiceClient generic service clients will be converted to a WebServiceException as seen in this Integration test:
var client = new Soap12ServiceClient(ServiceClientBaseUri);
try
{
var response = client.Send<AlwaysThrowsResponse>(
new AlwaysThrows { Value = TestString });
Assert.Fail("Should throw HTTP errors");
}
catch (WebServiceException webEx)
{
var response = (AlwaysThrowsResponse) webEx.ResponseDto;
var expectedError = AlwaysThrowsService.GetErrorMessage(TestString);
Assert.That(response.ResponseStatus.ErrorCode,
Is.EqualTo(typeof(NotImplementedException).Name));
Assert.That(response.ResponseStatus.Message,
Is.EqualTo(expectedError));
}

Resources