I am running into an unexpected CamelExchangeException with Apache Camel, Netty4 component, and load balancing. While the code is working fine and the response is returned, Camel still throws CamelExchangeException on the last exchange when a Netty connection closes.
from("netty4:tcp://localhost:9090?sync=true&textline=true")
.setBody(simple("pong"))
.setHeader(NettyConstants.NETTY_CLOSE_CHANNEL_WHEN_COMPLETE, simple("true", Boolean.class));
from("timer:foo?delay=10000")
.to("log:inbound?showAll=true")
.process((Exchange exchange) -> {
exchange.getIn().setBody("ping");
})
.loadBalance(new LoadBalancer())
.to("netty4:tcp://localhost:9090?sync=true&textline=true")
.end()
.to("log:outbound?showAll=true");
Here is the load balancer
public class LoadBalancer extends SimpleLoadBalancerSupport {
#Override
public void process(Exchange exchange) throws Exception {
this.getProcessors().get(0).process(exchange);
}
}
Exception
org.apache.camel.CamelExchangeException: No response received from remote server: localhost:9090. Exchange[ID-DESKTOP-6JJUOT9-1529441233240-0-21]
at org.apache.camel.component.netty4.handlers.ClientChannelHandler.channelInactive(ClientChannelHandler.java:133)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:245)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:231)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelInactive(AbstractChannelHandlerContext.java:224)
at io.netty.channel.ChannelInboundHandlerAdapter.channelInactive(ChannelInboundHandlerAdapter.java:75)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:245)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:231)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelInactive(AbstractChannelHandlerContext.java:224)
at io.netty.handler.codec.ByteToMessageDecoder.channelInputClosed(ByteToMessageDecoder.java:377)
at io.netty.handler.codec.ByteToMessageDecoder.channelInactive(ByteToMessageDecoder.java:342)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:245)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:231)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelInactive(AbstractChannelHandlerContext.java:224)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelInactive(DefaultChannelPipeline.java:1409)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:245)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:231)
at io.netty.channel.DefaultChannelPipeline.fireChannelInactive(DefaultChannelPipeline.java:927)
at io.netty.channel.AbstractChannel$AbstractUnsafe$8.run(AbstractChannel.java:822)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:463)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:886)
at java.lang.Thread.run(Thread.java:745)
The same exception is not thrown if load balancer is taken out or connection is to remain open.
Camel version 2.21.1
Related
I have a problem to manage the RabbitMQ and Database transaction in case the exchange is not found. This is the simple sequence:
Put message to Exchange
Mark as sent the sent message in database
When the Exchange is not found, the message is not sent, however the row is updated in database, which doesn't respect the transactional behavior.
Transaction are correctly managed for other error cases (DB error or RabbitMQ not available).
How can I manage this use case as transactional processing?
Transaction enabled in configuration:
#Bean
#ConditionalOnMissingClass("org.springframework.orm.jpa.JpaTransactionManager")
public RabbitTransactionManager rabbitTransactionManager(ConnectionFactory connectionFactory) {
return new RabbitTransactionManager(connectionFactory);
}
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(jacksonMessageConverter());
template.setChannelTransacted(true);
return template;
}
My service:
#Override
#Transactional
public void push(Message message) {
rabbitTemplate.convertAndSend(
"MessageExchange",
"binding.key",
objectMapper.writeValueAsString(message));
repository.markAsSent(message.getId());
}
Error fired after leaving method, not in the rabbitTemplate.convertAndSend method :
[AMQP Connection] ERROR o.s.a.r.c.CachingConnectionFactory.log : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'MessageExchange' in vhost '/', class-id=60, method-id=40)
[ThreadPoolTaskScheduler1] ERROR o.s.t.s.TransactionSynchronizationUtils.invokeAfterCompletion : TransactionSynchronization.afterCompletion threw exception
java.lang.IllegalStateException: Channel closed during transaction
at org.springframework.amqp.rabbit.connection.CachingConnectionFactory$CachedChannelInvocationHandler.invoke(CachingConnectionFactory.java:1171)
at com.sun.proxy.$Proxy143.txCommit(Unknown Source)
at org.springframework.amqp.rabbit.connection.RabbitResourceHolder.commitAll(RabbitResourceHolder.java:153)
at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils$RabbitResourceSynchronization.afterCompletion(ConnectionFactoryUtils.java:332)
I'm using deadLetterChannel to take care of exceptions and send them to the error queue.
errorHandler(deadLetterChannel(QUEUE_ERROR).maximumRedeliveries(3).redeliveryDelay(2000));
Is it possible to enrich the message with additional message headers? Or do i have to use onException for it?
You can use onRedelivery and with a processor to add headers before redelivering
errorHandler(deadLetterChannel(QUEUE_ERROR).maximumRedeliveries(3).redeliveryDelay(2000).onRedelivery(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
//add headers here
}
}));
Example route:
onException(Exception.class)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println("it works");
}
})
.handled(true);
from("jetty://http://0.0.0.0:8888/test")
.idempotentConsumer(header("myid"), MemoryIdempotentRepository.memoryIdempotentRepository(1000000))
.skipDuplicate(false)
.filter(property(Exchange.DUPLICATE_MESSAGE).isEqualTo(true))
.throwException(new DuplicateRequestException())
.end();
Sending a request to the listener URL without myid parameter throws org.apache.camel.processor.idempotent.NoMessageIdException: No message ID could be found using expression: header(myid) on message exchange: Exchange[Message: [Body is instance of org.apache.camel.StreamCache]]
without ever passing from onException.
Yes this is in fact a bug in Apache Camel. I have logged a ticket to get this fixed in the next releases.
https://issues.apache.org/jira/browse/CAMEL-7990
I have a route like following
from(direct:start)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.setProperty("doc_url", "http://myhost:5984/test/record/doc.csv");
}
}).setHeader(Exchange.HTTP_METHOD, constant("GET"))
.convertBodyTo(String.class)
.recipientList(header("doc_url")
.split().streaming.process(new MyProcessor());
I don't want to run apache couchdb every time for testing. I want to make this http endpoint refer to resource file in the codebase. How to write this?
you can use the Camel AdviceWith feature to intercept/replace endpoints for testing...
camelContext.getRouteDefinition("myRouteId")
.adviceWith(camelContext, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception
{
interceptSendToEndpoint("couchdb:http://localhost/database)
.skipSendToOriginalEndpoint()
.to("http://localhost:5984/test/record/doc.csv");
}
});
The route :
from("direct:start")
.setProperty(Exchange.CHARSET_NAME, constant("iso-8859-1"))
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message m = exchange.getOut();
m.setBody(exchange.getIn().getBody());
m.setHeader(Exchange.HTTP_METHOD, HttpMethods.POST);
m.setHeader(Exchange.CONTENT_ENCODING, "gzip" );
m.setHeader(Exchange.CONTENT_LENGTH, m.getBody(byte[].class).length );
m.setHeader(HttpHeaders.CONTENT_TYPE, "application/xml");
m.setHeader(Exchange.HTTP_CHARACTER_ENCODING, "iso-8859-1");
m.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate");
}
})
.marshal().gzip()
.to("http4://remote.com/path")
.unmarshal().gzip();
What I am sending :
String body = "<?xmlversion=\"1.0\"encoding=\"ISO-8859-1\"?><theXml></theXml>";
producer.sendBody(body);
I am getting
HTTP operation failed invoking http://remote.com/path with statusCode: 411
What is missing/wrong with this route ?
EDIT
The correct route would be
from("direct:start")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message m = exchange.getOut();
m.setBody(exchange.getIn().getBody());
m.setHeader(Exchange.HTTP_METHOD, HttpMethods.POST);
m.setHeader(Exchange.CONTENT_ENCODING, "gzip" );
m.setHeader(Exchange.CONTENT_TYPE, "application/xml");
}
})
// http4 takes care of compressing/decompressing gzip
.to("http4://remote.com/path")
But now I have another problem : the remote server does not handle "Transfer-Encoding: Chuncked" Which seems to be the default way camel-http4 does it.
And i can't figure out how to turn Chunked off.
See next question How to turn off “Transfer-Encoding Chuncked” in Camel-http4?
You are setting the content length from the length of the unencoded data. It should probably be the length of the transmitted data. Refer to this SO question:
content-length when using http compression
By the way, do you really need to gzip with the data format?
There is a Unit test in camel sending GZIPed data.
https://svn.apache.org/repos/asf/camel/trunk/components/camel-http4/src/test/java/org/apache/camel/component/http4/HttpCompressionTest.java