Use Netty to proxy your requests

Are you looking for a proxy to debug your client-server communication ? look no further! Netty has ready to run examples to do the job for you!

Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP/IP socket server. You can find here a startup tutorial about Netty.

In Netty, the main injection point into your code or business logic is achieved by means of handlers.  Handlers are based on the interceptor pattern just like filters do in a traditional servlet-based web application.  Handlers provide an event model that allows an application to monitor incoming/outgoing data, modify the data, convert the data, act upon the data, etc.  In short, handlers allow you to completely abstract separate concerns into separate classes.

Handlers are added to the pipeline using a specific order.  The order determines how and when the handlers are invoked.  So if one handler depends on another handler (a compression handler for example) then, you need to make sure that the codec handler comes before in the pipeline.  As data enters the system, often times asynchronously, the data is wrapped in a channel buffer object.  The object is then flowed from the first handler downstream to the last handler (unless a handler chooses to break the flow or an exception is thrown).

 Here’s a diagram taken from the Netty documentation:


In the following example, we will show how to proxy requests between a client and a server using the HexDumpProxy which is part of Netty examples. Here’s the main class:

package org.jboss.netty.example.proxy;

import java.net.InetSocketAddress;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.socket.ClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

public class HexDumpProxy {

    public static void main(String[] args) throws Exception {
        // Validate command line options.
        if (args.length != 3) {
            System.err.println(
                    "Usage: " + HexDumpProxy.class.getSimpleName() +
                    " <local port> <remote host> <remote port>");
            return;
        }

        // Parse command line options.
        int localPort = Integer.parseInt(args[0]);
        String remoteHost = args[1];
        int remotePort = Integer.parseInt(args[2]);


        System.err.println(
                "Proxying *:" + localPort + " to " +
                remoteHost + ':' + remotePort + " ...");

        // Configure the bootstrap.
        Executor executor = Executors.newCachedThreadPool();
        ServerBootstrap sb = new ServerBootstrap(
                new NioServerSocketChannelFactory(executor, executor));

        // Set up the event pipeline factory.
        ClientSocketChannelFactory cf =
                new NioClientSocketChannelFactory(executor, executor);

        sb.setPipelineFactory(
                new HexDumpProxyPipelineFactory(cf, remoteHost, remotePort));

        // Start up the server.
        sb.bind(new InetSocketAddress(localPort));
    }
}

As you can see, this class does barely three things:

  • Bootstrap a Server
  • Creates a custom Socket Pipeline (HexDumpProxyPipeline) from the ClientSocketChannelFactory and assign it to the server
  • Bind the server to an IP Address

Here’s the HexDumpProxyPipelineFactory which register itself at the end of the Channel Pipeline

package org.jboss.netty.example.proxy;

import static org.jboss.netty.channel.Channels.*;

import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.socket.ClientSocketChannelFactory;


public class HexDumpProxyPipelineFactory implements ChannelPipelineFactory {

    private final ClientSocketChannelFactory cf;
    private final String remoteHost;
    private final int remotePort;

    public HexDumpProxyPipelineFactory(
            ClientSocketChannelFactory cf, String remoteHost, int remotePort) {
        this.cf = cf;
        this.remoteHost = remoteHost;
        this.remotePort = remotePort;
    }

    public ChannelPipeline getPipeline() throws Exception {
        ChannelPipeline p = pipeline(); // Note the static import.
        p.addLast("handler", new HexDumpProxyInboundHandler(cf, remoteHost, remotePort));
        return p;
    }
}

And here’s the handler which manages the Channels and contains the logic to print the Hex of the data which is proxied.

package org.jboss.netty.example.proxy;

import java.net.InetSocketAddress;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.*;
import org.jboss.netty.channel.*;

import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.socket.ClientSocketChannelFactory;


public class HexDumpProxyInboundHandler extends SimpleChannelUpstreamHandler {

    private final ClientSocketChannelFactory cf;
    private final String remoteHost;
    private final int remotePort;

    private volatile Channel outboundChannel;

    public HexDumpProxyInboundHandler(
            ClientSocketChannelFactory cf, String remoteHost, int remotePort) {
        this.cf = cf;
        this.remoteHost = remoteHost;
        this.remotePort = remotePort;
    }

    @Override
    public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
            throws Exception {
        // Suspend incoming traffic until connected to the remote host.
        final Channel inboundChannel = e.getChannel();
        inboundChannel.setReadable(false);

        // Start the connection attempt.
        ClientBootstrap cb = new ClientBootstrap(cf);
        cb.getPipeline().addLast("handler", new OutboundHandler(e.getChannel()));
        ChannelFuture f = cb.connect(new InetSocketAddress(remoteHost, remotePort));

        outboundChannel = f.getChannel();
        f.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()) {
                    // Connection attempt succeeded:
                    // Begin to accept incoming traffic.
                    inboundChannel.setReadable(true);
                } else {
                    // Close the connection if the connection attempt has failed.
                    inboundChannel.close();
                }
            }
        });
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
            throws Exception {
        ChannelBuffer msg = (ChannelBuffer) e.getMessage();
        System.out.println(">>> " + ChannelBuffers.hexDump(msg));
        outboundChannel.write(msg);
    }

    @Override
    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
            throws Exception {
        if (outboundChannel != null) {
            closeOnFlush(outboundChannel);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
            throws Exception {
        e.getCause().printStackTrace();
        closeOnFlush(e.getChannel());
    }

    private static class OutboundHandler extends SimpleChannelUpstreamHandler {

        private final Channel inboundChannel;

        OutboundHandler(Channel inboundChannel) {
            this.inboundChannel = inboundChannel;
        }

        @Override
        public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
                throws Exception {
            ChannelBuffer msg = (ChannelBuffer) e.getMessage();
            System.out.println("<<< " + ChannelBuffers.hexDump(msg));
            inboundChannel.write(msg);
        }

        @Override
        public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
                throws Exception {
            closeOnFlush(inboundChannel);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
                throws Exception {
            e.getCause().printStackTrace();
            closeOnFlush(e.getChannel());
        }
    }

    /**
     * Closes the specified channel after all queued write requests are flushed.
     */
    static void closeOnFlush(Channel ch) {
        if (ch.isConnected()) {
            ch.write(ChannelBuffers.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
    }
}

Compile the project

In order to compile the class you need to add the Netty core library which is part of the JBoss AS 7 distribution.
If you are running a Maven project, then add the following dependency:

<dependency>
    <groupId>org.jboss.netty</groupId>
    <artifactId>netty</artifactId>
    <version>3.2.0.Final</version>
</dependency>

If you are running an Eclipse project, then simply add this JAR library:

Run the main class passing the required arguments- for example in order to debug JBoss’ HTTP requests (running on port 8080)
you can pass the following arguments: 8888 127.0.0.1 8080
Now point your browser to 127.0.0.1:8888 and you are done!

Proxying as text data

Proxing Hex data can be useful for debugging some kind of network protocols, however if you are simply looking for a textual view of proxied data then you need just a couple of adaptations to your code, such as converting the message to String, passing as argument the default Charset. Replace the two messageReceived methods for the inboundChannel and outboundChannel as follows:

  public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
                throws Exception {
            ChannelBuffer msg = (ChannelBuffer) e.getMessage();

            int len = msg.readUnsignedByte();
            int whichClient = msg.readUnsignedShort();
            assert len == msg.readableBytes();
            System.out.println(whichClient +" <<< " + msg.toString(Charset.defaultCharset()));

            inboundChannel.write(msg);
}
. . . .
  public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
            throws Exception {
        ChannelBuffer msg = (ChannelBuffer) e.getMessage();

        int len = msg.readUnsignedByte();
        int whichClient = msg.readUnsignedShort();
        assert len == msg.readableBytes();
        System.out.println(whichClient +" <<< " + msg.toString(Charset.defaultCharset()));

       outboundChannel.write(msg);
}

JBoss Netty tutorial

The Netty project provides an asynchronous event-driven network application framework and tooling for the rapid development of maintainable high-performance high-scalability protocol servers and clients. In this tutorial we we will show how to use it to enhance the application server capabilities.

 

So, Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server development.

 ‘Quick and easy’ does not mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise.

In the product documentation you can find some basic and advanced examples of Netty Servers: our aim is to show how to run a basic Server example at Server startup. JBoss AS by itself does not provide a custom Startup class, however this can be easily achieved by deploying an EJB 3.1 compatible marked with @Startup


import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;

import com.sample.NettyServer;

@Singleton(name="NettyEJB")
@Startup
public class NettyEJB {
@PostConstruct

        void init() {

         try {
                new NettyServer().startServer();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
}

And here’s a minimal Netty Echo server which listen for requests on port 8888:

package com.sample;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.*;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

public class NettyServer {

    public void startServer() throws Exception {
        ChannelFactory factory =
            new NioServerSocketChannelFactory(
                    Executors.newCachedThreadPool(),
                    Executors.newCachedThreadPool());

        ServerBootstrap bootstrap = new ServerBootstrap(factory);

        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() {
                return Channels.pipeline(new EchoServerHandler());
            }
        });

        bootstrap.setOption("child.tcpNoDelay", true);
        bootstrap.setOption("child.keepAlive", true);

        bootstrap.bind(new InetSocketAddress(8888));
        System.out.println("Server Started!");
    }
}

ChannelFactory is a factory which creates and manages Socket Channels and its related resources. It processes all I/O requests and performs I/O to generate ChannelEvents. Netty provides various ChannelFactory implementations: in this example, we are using NioServerSocketChannelFactory.

Next, the ServerBootstrap is a helper class that sets up a server. The bootstrap class as it is does not provide any special functionalities. In particular we need to define what happens when a new connection is received.  Let’s  create a new ChannelPipeline will be created by the specified ChannelPipelineFactory. The new pipeline contains the EchoServerHandler. In more complex applications, it is likely that you will add more handlers to the pipeline and extract this anonymous class into a top level class eventually.

EchoServerHandler extends SimpleChannelHandler, which is an implementation of ChannelHandler. SimpleChannelHandler provides various event handler methods that you can override. For now, it is just enough to extend SimpleChannelHandler rather than to implement the handler interfaces by yourself.
We override the messageReceived event handler method here. This method is called with a MessageEvent, which contains the received data, whenever new data is received from a client.


package com.sample;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class EchoServerHandler extends SimpleChannelHandler { 

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { 
         ChannelBuffer  buf = (ChannelBuffer) e.getMessage();
            while(buf.readable()) {
                System.out.println((char) buf.readByte());
                System.out.flush();
            }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) { 
        e.getCause().printStackTrace();
        
        Channel ch = e.getChannel();
        ch.close();
    }
}

We are ready to go now. What’s left is to bind to the port and to start the server. Here, we bind to the port 8888 of all NICs (network interface cards) in the machine. You can now call the bind method as many times as you want (with different bind addresses.)

Before deploying your EJB on the server, JBoss AS 7 users still miss one thing: the Netty libraries are not classified as automatic dependencies, therefore we need telling our application to trigger org.jboss.netty module. Just add to your MANIFEST.MF file the following:

 

 

Manifest-Version: 1.0
Dependencies: org.jboss.netty

 

You can connect with any client: the simplest one is telnet 8888

Or if you prefer use any Socket client, like this NIO client:


package com.sample;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

public class NIOClient {

    SelectionKey selkey=null;
    Selector sckt_manager=null;
    public static void coreClient()
    {

        BufferedReader stdin=new BufferedReader(new InputStreamReader(System.in));
        SocketChannel sc = null;
        try
        { sc = SocketChannel.open();
        sc.configureBlocking(false);       
        sc.connect(new InetSocketAddress(8888));  
        while (!sc.finishConnect())
        {   
        } 

        System.out.println("Enter the text");
        String HELLO_REQUEST =stdin.readLine().toString();

        System.out.println("Sending a request to HelloServer");    
        ByteBuffer buffer = ByteBuffer.wrap(HELLO_REQUEST.getBytes());    
        sc.write(buffer); 


        } 
        catch (IOException e) 
        {          
            e.printStackTrace();    
        }
        finally
        {       
            if (sc != null)
            {            
                try 
                {             
                    sc.close();            
                }
                catch (Exception e)
                {           
                    e.printStackTrace();       
                }       
            } 
        }   }

    public static void main(String args[]) {
        coreClient();

    }
}

That’s just a first taste of this awesome library, which allows advanced Server features like Asynchronous I/O API, TimeBased servers, an extensible event model, built-in security and much more!

For futher reading: http://docs.jboss.org/netty/3.2/guide/html/index.html