It seems to me that netty has its own exception handlers and they don't propagate exceptions (ie. IOException) back to camel route. Is there any way to know that client has disconnected?
Answering my own question.
My problem was releasing clients that would just wait forever to get some kind of response from netty mostly in case of connections closed by remote hosts during processing the pipeline.
What needs to be done is to add a custom handler to the pipeline that should extend ChannelDuplexHandler and override connect and write methods or SimpleChannelInboundHandler and override channelInactive. I used ChannelDuplexHandler.
public class ExceptionHandler extends ChannelDuplexHandler {
private final NettyProducer producer;
#Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise)
throws Exception {
ctx.connect(remoteAddress, localAddress, promise)
.addListener((future -> {
if (!future.isSuccess()) {
// no need to do anything here, camel will manage it on its own
}
}));
}
#Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
ctx.write(msg, promise).addListener(future -> {
if (!future.isSuccess()) {
reportStatusBackToCamel(ctx);
}
});
}
private void reportStatusBackToCamel(ChannelHandlerContext ctx) {
NettyCamelState nettyCamelState = producer.getCorrelationManager().getState(ctx, ctx.channel(),
new IOException());
Exchange exchange = nettyCamelState.getExchange();
AsyncCallback callback = nettyCamelState.getCallback();
exchange.setException(new RuntimeException("Client disconnected"));
callback.done(false);
}
}
In case of SimpleChannelInboundHandler just put exchange handling into channelInactive method.
In your ClientInitializerFactory in initChannel you add this handler to the pipeline:
pipeline.addLast(new ExceptionHandler(producer));
producer is given to you on application startup. If you need additional spring injected beans as I did, you simply end up having a couple of constructors in your factory class, one #Autowired (with your injected fields) calling the other setting additional producer field.
Related
I'm quite new with netty, I want to create a TCP server which does a custom application layer handshaking when a connection is to be instantiated. After the handshaking I want to pass the messages (ByteBuf) to a queue so that they could be processed by some other threads.
My question is, can I have multiple ChannelInboundHandlerAdapter's in the channel pipeline? one for the application layer handshaking protocol and the other one for passing the message to the queue. Furthermore I want to know how the messages flow through the pipeline. If a message is received at one handler (or decoder/encoder) how is it passed to another handler.
Specifically, if I change the EchoServer from here and add another ChannelInboundHandlerAdapter, the echo server handler would stop receiving any messages.
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
#Override
public void channelRead(ChannelHandlerContext ctx,
Object msg) {
}
});
ch.pipeline().addLast(
new EchoServerHandler());
}
});
My logic is: have 2 ChannelInboundHandlerAdapter's then do the handshaking with the first handler and discard packets if they do not match the handshaking criteria, and then pass the messages to a queue through the second ChannelInboundHandlerAdapter. Is my logic correct? If not how should it be?
Thank you very much.
ChannelInboundHandlerAdapter is an adapter class to the ChannelInBoundHandler interface. For beginning you can use SimpleChannelInboundHandler (or more complicated you can extend the adapter class writing your own handler that extends ChannelInboundHandlerAdapter ).
The SimpleCHannelInboundHandler releases the message automatically after channelRead() (and thereby passes it to the next handler in the ChannelPipeline).
For using the easier SimpleChannelInboundHandler see this thread Netty hello world example not working
So instead of this ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {}
you have to write a new class that extends SimpleChannelInboundHandler like
public class MyHandler extends SimpleChannelInboundHandler{
#Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
System.out.println(in.toString(io.netty.util.CharsetUtil.US_ASCII));
} finally {
in.release();
}
}
}
and invoke it like
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new MyHandler());
}
As said above the SimpleCHannelInboundHandler releases the message automatically after channelRead() (and thereby passes it to the next handler in the ChannelPipeline).
If you use ChannelInboundHandlerAdapter you have to implement the passing of the message/event to the next handler yourself
A handler has to invoke the event propagation methods in ChannelHandlerContext ctx to forward an event to its next handler. (in the SimpleChannelInboundHandler class this is implemented yet)
public class MyInboundHandler extends ChannelInboundHandlerAdapter {
#Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("Connected!");
ctx.fireChannelActive();
}
}
See this http://netty.io/4.0/api/io/netty/channel/ChannelPipeline.html
I must remind that:
Only One SimpleChannelInboundHandler extention can be add to the pipeline chain.
Because SimpleChannelInboundHandler have a finally code block will release all the msg.
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
#SuppressWarnings("unchecked")
I imsg = (I) msg;
channelRead0(ctx, imsg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
//release all handled messages,so the next handler won't be executed
ReferenceCountUtil.release(msg);**
}
}
}
Use ChannelInboundHandlerAdapter instead:
public class CustomizeChannelInboundHandler extends ChannelInboundHandlerAdapter {
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("do something you like!")
super.channelRead(ctx, msg);
}
}
I'm trying to implement a UDP server with Netty. The idea is to bind only once (therefore creating only one Channel). This Channel is initialized with only one handler that dispatches processing of incoming datagrams among multiple threads via an ExecutorService.
#Configuration
public class SpringConfig {
#Autowired
private Dispatcher dispatcher;
private String host;
private int port;
#Bean
public Bootstrap bootstrap() throws Exception {
Bootstrap bootstrap = new Bootstrap()
.group(new NioEventLoopGroup(1))
.channel(NioDatagramChannel.class)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.handler(dispatcher);
ChannelFuture future = bootstrap.bind(host, port).await();
if(!future.isSuccess())
throw new Exception(String.format("Fail to bind on [host = %s , port = %d].", host, port), future.cause());
return bootstrap;
}
}
#Component
#Sharable
public class Dispatcher extends ChannelInboundHandlerAdapter implements InitializingBean {
private int workerThreads;
private ExecutorService executorService;
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
DatagramPacket packet = (DatagramPacket) msg;
final Channel channel = ctx.channel();
executorService.execute(new Runnable() {
#Override
public void run() {
//Process the packet and produce a response packet (below)
DatagramPacket responsePacket = ...;
ChannelFuture future;
try {
future = channel.writeAndFlush(responsePacket).await();
} catch (InterruptedException e) {
return;
}
if(!future.isSuccess())
log.warn("Failed to write response packet.");
}
});
}
#Override
public void afterPropertiesSet() throws Exception {
executorService = Executors.newFixedThreadPool(workerThreads);
}
}
I have the following questions:
Should the DatagramPacket received by the channelRead method of the Dispatcher class be duplicated before being used by the worker thread? I wonder if this packet is destroyed after the channelRead method returns, even if a reference is kept by the worker thread.
Is it safe to share the Channel among all the worker threads and let them call writeAndFlush concurrently?
Thanks!
Nope. If you need the object to live longer you either turn it into something else or use ReferenceCountUtil.retain(datagram) and then ReferenceCountUtil.release(datagram) once you're done with it. You also shouldn't be doing await() at the executor service as well, you should register a handler for whatever happens.
Yes, channel objects are thread safe and they can be called from many different threads.
As far as I know netty handle exceptions by override method exceptionCaught(). But what I want is a Handler that can handler all exceptions in or out. So, the pipeline should be like :
InboundExceptionHandler - inboundHandler1 - inboundHandler2 - outboundHandler1 - outboundHandler2 - OutboundExceptionHandler
That means I should place 2 exception Handler in my pipeline separated in head and tail. But I think it looks ugly. any better idea?
You could just have a single inbound and outbound exception handler at the top/tail of your pipeline. If you want to catch all exceptions you could do something like this (I'm assuming this is Netty 4.0):
import io.netty.channel.*;
import java.net.SocketAddress;
public class ExceptionHandler extends ChannelDuplexHandler {
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Uncaught exceptions from inbound handlers will propagate up to this handler
}
#Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
ctx.connect(remoteAddress, localAddress, promise.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) {
if (!future.isSuccess()) {
// Handle connect exception here...
Throwable failureCause = future.cause();
}
}
}));
}
#Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
ctx.write(msg, promise.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) {
if (!future.isSuccess()) {
// Handle write exception here...
Throwable failureCause = future.cause();
}
}
}));
}
// ... override more outbound methods to handle their exceptions as well
}
Any exceptions that are thrown by inbound handlers will propagate "up" the pipeline and invoke this handler's exceptionCaught() method, assuming a handler below does not consume them.
For outbound operations like write() and connect(), you need to add a ChannelFutureListener to catch their exceptions. The exceptionCaught() method is only invoked for exceptions from inbound events like channelRead(), channelActive() etc.
With this handler at the "top" of the pipeline we can catch exceptions from all outbound handlers below. Say one of your outbound handlers is doing some encoding and this fails with an exception, this will be handled by our channel future listener that we added to the write() operation's promise.
If this exception handler was installed at the "bottom"/head of the pipeline like you initially suggested, then it would not see exceptions from handlers above it, because its write() method would never be invoked if the write failed in a previous handler. That's why this handler must be at the top.
To hopefully avoid confusion about top/bottom of the pipeline, here is how I would configure your example pipeline:
pipeline.addLast(outboundHandler2) // bottom
.addLast(outboundHandler1)
.addLast(inboundHandler2)
.addLast(inboundHandler1)
.addLast(new ExceptionHandler()); // top
The final solution is to customize the ChannelInitializer You can even add more logic
Using Netty 4.0.27 & Java 1.8.0_20
So I am attempting to learn how Netty works by building a simple chat server (the typical networking tutorial program, I guess?). Designing my own simple protocol, called ARC (Andrew's Relay Chat)... so that's why you see ARC in the code a lot. K, so here's the issue.
So here I start the server and register the various handlers...
public void start()
{
System.out.println("Registering handlers...");
ArcServerInboundHandler inboundHandler = new ArcServerInboundHandler(this);
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try
{
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>()
{
#Override
public void initChannel(SocketChannel ch) throws Exception
{
ch.pipeline().addLast(new ArcDecoder(), inboundHandler);
ch.pipeline().addLast(new ArcEncoder());
}
}).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);
try
{
System.out.println("Starting Arc Server on port " + port);
ChannelFuture f = bootstrap.bind(port).sync();
f.channel().closeFuture().sync();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
finally
{
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
My "inboundHandler" does get called when the user connects.
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception
{
System.out.println("CLIENT CONNECTED"); // THIS PRINTS, REACHES THIS POINT
ArcPacket packet = new ArcPacket();
packet.setArc("PUBLIC_KEY");
packet.setField("KEY", Crypto.bytesToHex(server.getRsaKeys().getPublic().getEncoded()));
ctx.writeAndFlush(packet);
}
This is my encoder, which does not seem to get called at all...
public class ArcEncoder extends MessageToByteEncoder<ArcPacket>
{
#Override
protected void encode(ChannelHandlerContext ctx, ArcPacket msg, ByteBuf out) throws Exception
{
System.out.println("ENCODE"); // NEVER GETS HERE
String message = ArcPacketFactory.encode(msg);
byte[] data = message.getBytes("UTF-8");
out.writeBytes(data);
System.out.println("WROTE");
}
#Override
public boolean acceptOutboundMessage(Object msg) throws Exception
{
System.out.println("ACCEPT OUTBOUND MESSAGE"); // NEVER GETS HERE
return msg instanceof ArcPacket;
}
}
So,
The code that calls ctx.writeAndFlush(packet); is run, but it doesn't seem to invoke the encoder at any point. Am I missing something obvious? Perhaps I'm adding the encoder incorrectly? Though it looks right when I compare it to other examples I've seen.
Thanks for any help.
Your encoder (ArcEncoder) is placed after your inbound handler. It means, the ctx.*() method calls will never be evaluated by the encoder. To fix your problem, you have to move the ArcEncoder before the inbound handler:
ch.pipeline().addLast(new ArcDecoder(), new ArcEncoder(), inboundHandler);
For more information about the event evaluation order, please read the API documentation of ChannelPipeline.
I think the problem is that you're using the ChannelHandlerContext to write to the Channel. What this does is to insert the message in the pipeline at the point of your handler, going outbound. But since your decoder is added before your encoder in the pipeline this means that anything you write using the decoder context will be inserted after the encoder in the pipeline.
The correct way to do it to ensure that the encoder is called is to call:
ctx.channel.writeAndFlush()
I am working through some examples of using the Netty io server. I am working on understanding the pipeline construct, and how elements that are added to the pipeline effect each other, and how their order is important in the pipeline. So far I have created a child handler as follows in my main method
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline().addLast( ... );
}
}
I want to have a pipeline where one handler generates a long with System.currentTimeMillis() and another handler that encodes it as a string in the format provided by the Date classes toString method. To that end I started with this:
ChannelPipeline pipeline = socketChannel.pipeline().addLast(
new ChannelInboundHandlerAdapter() {
#Override
public void channelActive(final ChannelHandlerContext channelHandlerContext) {
byte[] bytes = "Press return to get the time\n".getBytes();
channelHandlerContext.writeAndFlush(
channelHandlerContext
.alloc()
.buffer(bytes.length)
.writeBytes(bytes));
}
#Override
public void channelRead(final ChannelHandlerContext channelHandlerContext, Object object) {
try {
ChannelFuture channelFuture = channelHandlerContext.writeAndFlush(
channelHandlerContext
.alloc()
.buffer()
.writeLong(System.currentTimeMillis()));
channelFuture.addListener(ChannelFutureListener.CLOSE);
} finally {
ReferenceCountUtil.release(object);
}
}
#Override
public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) {
throwable.printStackTrace();
channelHandlerContext.close();
}
}
);
How do I modify this so that an encoder encodes the payload as a date string such as 2014-10-07T18:34:10.897-00:00? Is this the correct way to think about the pipeline, though a trivial example?
In terms of understanding, looking at this example, it is unclear how the order of this pipeline is executed. For example, if I assume they execute in linear order, I would thing some data payload is gzipped and then immediately ungzipped. What am I missing?
pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
// Add the number codec first,
pipeline.addLast(new BigIntegerDecoder());
pipeline.addLast(new NumberEncoder());
// and then business logic.
// Please note we create a handler for every new channel
// because it has stateful properties.
pipeline.addLast(new FactorialServerHandler());
Update: I found this diagram but I am still trying to work out how to coerce the long in the example above to a date on the way back to the client. How would I do that?