How can I handle exceptions in an http outbound gateway?
When i receive status code 500 or 400..., an exception is shown. So What should I do to handle http error using spring integration.
My configuration is like:
<int:inbound-channel-adapter channel="quakeinfotrigger.channel"
expression="''">
<int:poller fixed-delay="60000"></int:poller>
</int:inbound-channel-adapter>
<int:channel id="quakeinfo.channel">
<int:queue capacity="10" />
</int:channel>
<int:channel id="quakeinfotrigger.channel"></int:channel>
<int:channel id="error.channel">
<int:queue capacity="10" />
</int:channel>
<int:service-activator input-channel="error.channel"
ref="httpResponseErrorHandler" method="handleMessage">
<int:poller fixed-delay="5000"></int:poller>
</int:service-activator>
<int:service-activator input-channel="quakeinfo.channel"
ref="httpResponseMessageHandler" method="handleMessage">
<int:poller fixed-delay="5000"></int:poller>
</int:service-activator>
<int:gateway id="requestGateway" service-interface="standalone.HttpRequestGateway"
default-request-channel="quakeinfotrigger.channel" error-channel="error.channel" />
<int-http:outbound-gateway id="quakerHttpGateway"
request-channel="quakeinfotrigger.channel" url="http://fooo/mmmm/rest/put/44545454"
http-method="PUT" expected-response-type="java.lang.String" charset="UTF-8"
reply-timeout="5000" reply-channel="quakeinfo.channel">
</int-http:outbound-gateway>
<bean id="httpResponseMessageHandler" class="standalone.HttpResponseMessageHandler" />
<bean id="httpResponseErrorHandler" class="standalone.HttpResponseErrorHandler" />
I would like to know why exception does'nt go to reply-channel
I would like to know why exception does'nt go to reply-channel
Because it's natural to handle exceptions as, er, Exceptions.
There are (at least) two ways to handle exceptions in Spring Integration.
Add an error-channel and associated flow on whatever starts your flow (e.g. a gateway). The error channel gets an ErrorMessage with a MessagingException payload; the exception has two properties - the failedMessage and the cause.
Add a ExpressionEvaluatingRequestHandlerAdvice (or a custom advice) to the gateway; see Adding Behavior to Endpoints.
If the response status code is in the HTTP series CLIENT_ERROR or SERVER_ERROR, HttpClientErrorException or HttpServerErrorException are thrown respectively. Hence the response doesn't go to reply channel
Refer DefaultResponseErrorHandler
Methods: hasError and handleError
To Handle these exceptions, create your own CustomResponseErrorHandler and override contents of hasError and handlerError methods.
public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return hasError(getHttpStatusCode(response));
}
protected boolean hasError(HttpStatus statusCode) {
return /*Status Code to be considered as Error*/ ;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException { /* Handle Exceptions */
}
}
And add error-handler to your http-outbound-gateway
<int-http:outbound-gateway id="quakerHttpGateway"
request-channel="quakeinfotrigger.channel" url="http://fooo/mmmm/rest/put/44545454"
http-method="PUT" expected-response-type="java.lang.String" charset="UTF-8"
reply-timeout="5000" reply-channel="quakeinfo.channel" error-handler="customResponseErrorHandler">
</int-http:outbound-gateway>
I had occurred the same problem.I threw my own customized Exception and error message but always got "500 Internal server error".It's because there will be no reply message and "reply" is timeout when throw Exception.So I handle the exception by subscribing error-channel and then reply by myself.
Message<?> failedMessage = exception.getFailedMessage();
Object replyChannel = new MessageHeaderAccessor(failedMessage).getReplyChannel();
if (replyChannel != null) {
((MessageChannel) replyChannel).send(MessageFactory.createDataExchangeFailMessage(exception));
}
Related
I have int-aws:sqs-message-driven-channel-adapter on which if I set the errorChannel, the downstream exceptions go there.
However, when I don't set an errorChannel, the exception does not get logged. It does not go to the errorChannel which is expected. Is there a way, that such exceptions at least get logged? Is there a default errorlogger which can simply log such errors?
UPDATE
Posting XML and DSL config as per the comments. The error is simulated in the persistence layer by setting null for a #NotBlank field on the ServiceObject.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-aws="http://www.springframework.org/schema/integration/aws"
xmlns:int-jpa="http://www.springframework.org/schema/integration/jpa"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jpa https://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd
http://www.springframework.org/schema/integration/aws https://www.springframework.org/schema/integration/aws/spring-integration-aws.xsd">
<int:channel id="serviceLogChannel">
<int:interceptors>
<int:wire-tap channel="loggingChannel"/>
</int:interceptors>
</int:channel>
<int-aws:sqs-message-driven-channel-adapter sqs="amazonSQS"
auto-startup="true"
channel="serviceLogChannel"
id="sqsMessageDrivenChannelAdapter"
queues="${app.queue-name}"
max-number-of-messages="10"
visibility-timeout="5"
wait-time-out="20"
error-channel="errorChannel"/>
<int:chain input-channel="serviceLogChannel">
<int:json-to-object-transformer type="ServiceObject"/>
<int-jpa:outbound-channel-adapter entity-class="ServiceObject"
persist-mode="PERSIST"
entity-manager-factory="entityManagerFactory">
<int-jpa:transactional/>
</int-jpa:outbound-channel-adapter>
</int:chain>
<int:logging-channel-adapter log-full-message="true"
logger-name="tapInbound"
id="loggingChannel"/>
<int:service-activator input-channel="errorChannel" expression="#reThrow.rethrow(payload)" order="100"/>
</beans>
The ReThrow service-activator:
#Component
public class ReThrow {
public void rethrow(Exception exception) throws Exception {
throw exception;
}
}
The DSL config for the same is :
#Configuration
public class IntegrationConfiguration {
#Bean
public MessageProducer createSqsMessageDrivenChannelAdapter(
AmazonSQSAsync amazonSQSAsync,
MessageChannel serviceChannel,
MessageChannel errorChannel,
#Value("${app.queue-name}") String queueName) {
SqsMessageDrivenChannelAdapter adapter =
new SqsMessageDrivenChannelAdapter(amazonSQSAsync, queueName);
adapter.setVisibilityTimeout(5);
adapter.setWaitTimeOut(20);
adapter.setAutoStartup(true);
adapter.setMaxNumberOfMessages(10);
adapter.setOutputChannel(serviceChannel);
adapter.setErrorChannel(errorChannel);
adapter.setMessageDeletionPolicy(SqsMessageDeletionPolicy.NO_REDRIVE);
return adapter;
}
#Bean
public IntegrationFlow messageProcessingFlow(
MessageChannel serviceChannel, EntityManagerFactory entityManagerFactory) {
return IntegrationFlows.from(serviceChannel)
.transform(Transformers.fromJson(ServiceObject.class))
.handle(
Jpa.outboundAdapter(entityManagerFactory)
.entityClass(ServiceObject.class)
.persistMode(PersistMode.PERSIST),
e -> e.transactional())
.get();
}
#Bean
public IntegrationFlow errorProcessingFlow(MessageChannel errorChannel) {
return IntegrationFlows.from(errorChannel)
.handle(
m -> {
throw (RuntimeException) m.getPayload();
})
.get();
}
#Bean
public MessageChannel serviceChannel() {
return MessageChannels.publishSubscribe().get();
}
}
The SqsMessageDrivenChannelAdapter is fully based on the SimpleMessageListenerContainerFactory from Spring Cloud AWS and that one just delegates to a listener we provide. Looking to the code there is just no any error handling. So, the best way to deal with it at the moment to explicitly set an error-channel="errorChannel" and it is going to be logged via default logger subscribed to that global errorChannel.
And yes: it is not expected to go to the errorChannel by default. I'm not sure that there is such an official claim in our docs. Probably better to think about it as "no error channel by default", so it is up to underlying protocol client to handle thrown errors. Since there is no one there, then we don't have choice unless set error channel explicitly.
I have a SI application that uses Bridge to bridge some channels. I want the request headers to be copied through the bridge. When I debugged the application, I found that the BridgeHandler has a method which sets copyRequestHeaders() to false.
public class BridgeHandler extends AbstractReplyProducingMessageHandler {
#Override
public String getComponentType() {
return "bridge";
}
#Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return requestMessage;
}
#Override
protected boolean shouldCopyRequestHeaders() {
return false;
}
}
I want to change this to keep it true. I looked up Bridge's XSD and it doesnt seem to support any entry to alter this, nor does it take a reference to a custom bridge handler bean.
What are my options now? Any suggestions would be helpful.
spring-config.xml:
<beans:import resource="classpath*:spring/rabbit.xml" />
<beans:import resource="classpath*:spring/router.xml" />
<beans:import resource="classpath*:spring/file_persister.xml" />
<beans:import resource="classpath*:spring/transformer.xml" />
<!-- Rabbit to Router -->
<int:bridge id="rabbitBridge" input-channel="rabbitInboundChannel" output-
channel="routerInputChannel" />
rabbit.xml:
<int:channel id="rabbitInboundChannel" />
<rabbit:connection-factory id="amqpConnectionFactoryInbound"
host="${rabbit.host}" port="${rabbit.port}"
username="${rabbit.username}" password="${rabbit.password}" channel-cache-
size="${rabbit.channelCacheSize}"
connection-factory="rabbitConnectionFactoryInbound" />
<beans:bean id="rabbitConnectionFactoryInbound"
class="com.rabbitmq.client.ConnectionFactory">
<beans:property name="requestedHeartbeat"
value="${rabbit.requestedHeartBeat}" />
</beans:bean>
<!-- Inbound Adapter to AMQP RabbitMq -->
<int-amqp:inbound-channel-adapter id="rabbitMQInboundChannelAdapter"
channel="rabbitInboundChannel" concurrent-
consumers="${rabbit.concurrentConsumers}" task-executor="rabbit-
executor" connection-factory="amqpConnectionFactoryInbound"
message-converter="byteArrayToStringConverter" queue-
names="${rabbit.queue}" error-channel="errorChannelId"
prefetch-count="${rabbit.prefetchCount}" />
<header-enricher input-channel="rabbitInboundChannel" output-
channel="rabbitLoggingChannel">
<int:header name="Operation" value="${operation.rabbit}" overwrite="true" />
**<int:header name="GUID" expression="#{ 'T(java.util.UUID).randomUUID().toString()' }" />**
<int:header name="operationStartTime" expression="#{ 'T(java.lang.System).currentTimeMillis()' }" />
</header-enricher>
<int:channel id="rabbitLoggingChannel">
<int:interceptors>
<int:wire-tap channel="loggerChannel" />
</int:interceptors>
</int:channel>
<task:executor id="rabbit-executor" rejection-policy="CALLER_RUNS" pool-
size="${rabbit.poolSize}" queue-capacity="${rabbit.queueSize}" />
router.xml:
<int:channel id="routerInputChannel" />
<int:header-enricher input-channel="routerInputChannel" output-
channel="routerLoggingChannel">
<int:header name="Operation" value="${operation.router}" overwrite="true"
/>
<int:header name="file_name" expression="headers['GUID'] + '.xml'" />
<int:header name="operationStartTime" expression="#{
'T(java.lang.System).currentTimeMillis()' }" overwrite="true" />
<int:error-channel ref="errorChannelId" />
</int:header-enricher>
<int:recipient-list-router id="recipientListRouter" input-
channel="routerInputChannel">
<int:recipient channel="filePersistChannel" selector-expression="new
String(payload).length()>0" />
<int:recipient channel="transformerInputChannel" />
</int:recipient-list-router>
<int:channel id="routerLoggingChannel">
<int:interceptors>
<int:wire-tap channel="loggerChannel" />
</int:interceptors>
</int:channel>
Logging O/P:
2017-04-24 13:26:33,360 [rabbit-executor-4] INFO
Operation="RABBIT_INBOUND_ADAPTER" Status="Success" DurationMs="0"
GUID="8d5c67c8-a0fb-4a7e-99dc-f545159dde7e"
2017-04-24 13:26:33,361 [rabbit-executor-4] INFO Operation="ROUTER"
Status="Success" DurationMs="0" GUID=" "
2017-04-24 13:26:33,364 [rabbit-executor-4] INFO Operation="FILE_PERSISTER"
Status="Success" DurationMs="3" GUID=" "
2017-04-24 13:26:33,381 [rabbit-executor-5] INFO
Operation="ORDER_EVENT_TRANSFORMER" Status="Success" DurationMs="27"
GUID=" "
If you notice, the GUID header which gets set in rabbit.xml is not propogated to router.xml, hence the log has empty GUID. Without the bridge, the GUID gets printed.
You don't need to do that. The <bridge> is based on the BridgeHandler which logic is very transparent:
#Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return requestMessage;
}
So, it is obvious - your request headers are transferred, just because the whole requestMessage travels to the outputMessage.
The logic behind that method is like:
protected Message<?> createOutputMessage(Object output, MessageHeaders requestHeaders) {
AbstractIntegrationMessageBuilder<?> builder = null;
if (output instanceof Message<?>) {
if (!this.shouldCopyRequestHeaders()) {
return (Message<?>) output;
}
builder = this.getMessageBuilderFactory().fromMessage((Message<?>) output);
}
else if (output instanceof AbstractIntegrationMessageBuilder) {
builder = (AbstractIntegrationMessageBuilder<?>) output;
}
else {
builder = this.getMessageBuilderFactory().withPayload(output);
}
if (this.shouldCopyRequestHeaders()) {
builder.copyHeadersIfAbsent(requestHeaders);
}
return builder.build();
}
Since our output is Message and we don't copyRequestHeaders(), nothing is changed and everything is good for you.
I wonder what makes you to come to us with such an issue, because there is just no issue.
confused
I have some performance problem with spring integration tcp factory.
my application have about 70 clients which trying to send data through tcp connection. i used below configuration for tcp server using spring integration but in server i receive data every 5 seconds. but when i implement tcp socket manually without using spring integration i receive about 5 connections in every second. any idea about my problem ? i really want to use spring integration but i don't know how can i increase my performance.
<int:poller id="defaultPoller" default="true" tast-executor="defaultTaskExecutor" fixed-delay="500" />
<task:executor id="defaultTaskExecutor" pool-size="5-20" queue-capacity="50"/>
<bean id="CustomeSerializerDeserializer"
class="CustomeSerializerDeserializer" />
<task:executor id="tcpFactoryTaskExecutor" pool-size="5-20"
queue-capacity="20000" />
<int-ip:tcp-connection-factory id="tcpConnectionFactory"
type="server" port="5423"
single-use="false" so-timeout="5000" task-executor="tcpFactoryTaskExecutor"
serializer="CustomeSerializerDeserializer" deserializer="CustomeSerializerDeserializer" />
<int-ip:tcp-inbound-channel-adapter
id="tcpInboundAdapter" channel="requestChannel" connection-factory="tcpConnectionFactory" />
<int:channel id="requestChannel">
<int:queue capacity="50" />
</int:channel>
<int:service-activator input-channel="requestChannel"
output-channel="responseChannel" ref="MessageHandler" method="parse" />
<bean id="MessageHandler"
class="TCPMessageHandler" />
<int:channel id="responseChannel">
<int:queue capacity="50" />
<int:channel />
<int-ip:tcp-outbound-channel-adapter
id="tcpOutboundAdapter" channel="responseChannel" connection-factory="tcpConnectionFactory" />
UPDATE1: here is my custom serialize/deserializer class:
public class SerializerDeserializer extends AbstractByteArraySerializer{
#Override
public void serialize(byte[] object, OutputStream outputStream)
throws IOException {
if (object != null && object.length != 0) {
outputStream.write(object);
outputStream.flush();
}
}
#Override
public byte[] deserialize(InputStream inputStream) throws IOException {
int c = inputStream.read();
if (c!=0){
// 2 byte
byte[] configMessage = BinaryUtil.readNByteArrayFromStream(inputStream, c)/*(inputStream , c)*/;
return configMessage;
}
int d = inputStream.read();
if (d==0){
// 253 byte
byte[] dataMessage = BinaryUtil.readNByteArrayFromStream(inputStream,253);
return dataMessage;
}
// 15 byte
byte[] hanshakeMessage = BinaryUtil.readNByteArrayFromStream(inputStream,d);
return hanshakeMessage;
}
}
I suspect a problem with your custom deserializer 5 seconds is suspicious since your timeout is also 5 seconds - show the code and explain the protocol.
If the deserializer doesn't receive a full message it will time out.
Also turn on TRACE level logging for org.springframework.integration to debug this - if you can't figure it out from the trace, post the log file somewhere like pastebin and we'll take a look.
I have the below xml configuration in my application and I would like to convert it to the Java DSL.
So in this reference I'm explicitly defining the name for the error channel. Mostly for example reason. With this reference what I'm expecting to happen is when a downstream process throws and exception that it should route the payload back through the error channel. What would the Java code look like?
<int-jms:message-driven-channel-adapter
id="notification"
connection-factory="connectionFactory"
destination="otificationQueue"
channel="notificationChannel"
error-channel="error"
/>
<int:chain id="chainError" input-channel="error">
<int:transformer id="transformerError" ref="errorTransformer" />
<int-jms:outbound-channel-adapter
id="error"
connection-factory="connectionFactory"
destination="errorQueue" />
</int:chain>
#Bean
public IntegrationFlow jmsMessageDrivenFlow() {
return IntegrationFlows
.from(Jms.messageDriverChannelAdapter(this.jmsConnectionFactory)
.id("notification")
.destination(this.notificationQueue)
.errorChannel(this.errorChannel))
...
.get();
}
#Bean
public IntegrationFlow ErrorFlow() {
return IntegrationFlows
.from(this.errorChannel)
.transform(errorTransformer())
.handle(Jms.outboundAdapter(this.jmsConnectionFactory)
.destination(this.errorQueue), e -> e.id("error"))
.get();
}
EDIT:
#Bean
public MessageChannel errorChannel() {
return new DirectChannel();
}
and autowire it, or reference it as
.errorChannel(errorChannel())
I need to handle certain error conditions within my spring integration flow. My flow is using a message store and setting the error channel on the poller. I had thought that if I handled the message in the error handler that the rollback would not occur, but the messageStore remove (delete) is being rolled back before the error flow is even executed.
Here is a pseudo-flow that duplicates my issue.
<int:channel id="rollbackTestInput" >
<int:queue message-store="messageStore"/>
</int:channel>
<int:bridge input-channel="rollbackTestInput" output-channel="createException" >
<int:poller fixed-rate="50"
error-channel="myErrorChannel">
<int:transactional />
</int:poller>
</int:bridge>
<int:transformer input-channel="createException" output-channel="infoLogger"
expression="T(GarbageToForceException).doesNotExist()" />
<int:channel id="myErrorChannel">
<int:queue/>
</int:channel>
<!-- JDBC Message Store -->
<bean id="messageStore" class="org.springframework.integration.jdbc.JdbcMessageStore">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name="dataSource" ref="dataSource"/>
This flow will result in an infinite rollback/poll loop. How can I handle the error and not rollback?
That's correct behavior. The error-channel logic is accepted around the TX advice for the polling task.
The code looks like:
#Override
public void run() {
taskExecutor.execute(new Runnable() {
#Override
public void run() {
int count = 0;
while (initialized && (maxMessagesPerPoll <= 0 || count < maxMessagesPerPoll)) {
try {
if (!pollingTask.call()) {
break;
}
count++;
}
catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
else {
throw new MessageHandlingException(new ErrorMessage(e), e);
}
}
}
}
});
}
Where TX Advice is on the pollingTask.call(), but error handling is done from the taskExecutor:
this.taskExecutor = new ErrorHandlingTaskExecutor(this.taskExecutor, this.errorHandler);
Where your error-channel is configured on that errorHandler as MessagePublishingErrorHandler.
To reach your requirements you can try to follow with synchronization-factory on the <poller>:
<int:transaction-synchronization-factory id="txSyncFactory">
<int:after-rollback channel="myErrorChannel" />
</int:transaction-synchronization-factory>
Or supply your <int:transformer> with <request-handler-advice-chain>:
<int:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
<property name="onFailureExpression" value="#exception" />
<property name="failureChannel" value="myErrorChannel" />
<property name="trapException" value="true" />
</bean>
</int:request-handler-advice-chain>
I have found a different solution that meets my requirements and is less invasive.
Instead of using transactional on the poller, I can use an advice chain that that specifies which exceptions I should rollback and which I shouldn't. For my case, I do not want to rollback most exceptions. So I rollback Throwable and list any specific exceptions I want to rollback.
<int:bridge input-channel="rollbackTestInput"
output-channel="createException">
<int:poller fixed-rate="50"
error-channel="myErrorChannel">
<int:advice-chain>
<int:ref bean="txAdvice"/>
</int:advice-chain>
</int:poller>
</int:bridge>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" rollback-for="javax.jms.JMSException"
no-rollback-for="java.lang.Throwable" />
</tx:attributes>
</tx:advice>