Thursday, January 29, 2015

Using Netty Component in Apache Camel - Hello World Example

This tutorial will demo how to use Netty Component in Apache Camel. By using netty component with camel in the example, this article is going to achieve these goals:

  • Create a TCP Socket server as camel endpoint with netty component, waiting for TCP client.
  • Utilize camel to route the received TCP message from Netty endpoint to other process logic
  • Send the processed result back to the TCP client.

In this example, every time server get a string, it will reply with a prefix "Hello" to form a greeting. If "Tom" is received from client, "Hello Tom" will be send back to the client.

This example also shows there features:

  • Use Camel and Netty component with Spring framework.
  • Use POJO class for business logic, which does not depend on any Camel or Netty componenent APIs.

1. What you need

  • JDK 1.7+
  • Maven 3.2.1

The dependencies will be managed by Maven.

2. Configure the maven pom.xml

The key artifacts are camel-core, camel-spring, camel-netty. The camel-spring artifact is needed for using Spring framework. The camel-netty artifact is needed for using netty component in camel.

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.shengwang.example</groupId>
<artifactId>spring-camel-netty3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>spring-camel-netty</name>
<url>http://maven.apache.org</url>

<properties>
<!-- Generic properties -->
<java.version>1.7</java.version>

<!-- Camel -->
<camel.version>2.14.1</camel.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-netty</artifactId>
<version>${camel.version}</version>
</dependency>
</dependencies>


<build>
<plugins>
<!-- Use JDK 1.7 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>

<!-- Allows the routes to be run via 'mvn camel:run' -->
<plugin>
<groupId>org.apache.camel</groupId>
<artifactId>camel-maven-plugin</artifactId>
<version>${camel.version}</version>
</plugin>

</plugins>
</build>
</project>

In the POM file, Jave version and Camel version are defined in properties, followed by 3 dependencies. Then there are 2 plugins, the first one is to specify the Java version for building the project, the second plugin is needed to allow the camel route to be run without explicitly defined main function. It's very handy for demo and test.


3. Define a Java class to process the received message


This class is the process logic in this demo. This class is a POJO which means the business code is completely decoupled with camel or camel-netty component.

package com.shengwang.example.server;

import org.springframework.stereotype.Service;

@Service
public class EchoService {
public String sayHello(String guestName) {
System.out.println("Input guestName : "+ guestName);
return "Hello " + guestName;
}
}

The EchoService.java is very short and simple. Annotation @Service means it's will be registered in Spring context as a bean. How to use this bean will be explained later.


4. Define Camel route


Now Let's define another Java class for Camel route. This class extends RouteBuilder from Camel package and override the configure() method. In configure(), route is defined.

package com.shengwang.example.server;

import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.spring.Main;

public class ServerRoute extends RouteBuilder {
public static void main(String[] args) throws Exception {
new Main().run(args);
}

@Override
public void configure() throws Exception {
from("netty:tcp://localhost:7000?sync=true&allowDefaultCodec=false&encoder=#stringEncoder&decoder=#stringDecoder")
.to("bean:echoService");
}
}

There are two methods in the ServerRoute class. The first one is a main, so we can run the route directly with the helper class Main from camel-spring package.


The second method has the route defined. It means somehow like "from to ". In this tutorial we get message from netty endpoint and send to a Spring bean which defined in previous section. The is how the EchoService class is used in this demo. The interesting part is the long from URI:"netty:tcp://localhost:7000?sync=true&allowDefaultCodec=false&encoder=#stringEncoder&decoder=#stringDecoder". It can be break down bit by bit.


"netty:tcp://localhost:7000" means netty component is needed to setup a tcp server on localhost port 7000.


"sync=true" means exchange pattern is InOut, the server will send back the response to client.


"allowDefaultCodec=false" means we don't use default encoder and decoder for netty component. Because in reality, the message come in netty endpoint is most unlikely formed in that message format, so it need to be set to false against its default value true.


"encoder=#stringEncoder&decoder=#stringDecoder" means message comes into netty will be decoded before transferring to the business logic EchoService, and response from EchoService will be encoded before sending back to the client. In this tutorial, server receive and send back Java string, so the StringEncoder and StringDecoder from camel-netty package will be used. The hash tag "#" refer to beans in Spring context which will be defined in Spring configuration file later.


5. Spring configuration


The Spring configuration file app-context.xml is located in directory src/main/resources/META-INF/spring.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:camel="http://camel.apache.org/schema/spring"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd">

<context:component-scan base-package="com.shengwang.example" />

<!-- ================================== -->
<!-- Define camel context -->
<!-- ================================== -->
<camel:camelContext id="camel">
<!-- Location for route scan -->
<camel:package>com.shengwang.example.server</camel:package>
</camel:camelContext>

<!-- ================================== -->
<!-- Define netty codecs -->
<!-- ================================== -->
<!-- ChannelBuffer/ByteBuf to String -->
<bean id="stringDecoder" class="org.jboss.netty.handler.codec.string.StringDecoder"/>

<!-- String to ChannelBuffer/ByteBuf -->
<bean id="stringEncoder" class="org.jboss.netty.handler.codec.string.StringEncoder"/>

</beans>

<context:component-scan> scan for normal Spring beans of a package. Here is the EchoServer. Then,<camel:camelContext>, Camel context is defined so does the location of the route. Finally, encoder and decoder beans are defined.


Now all 4 files of the project have been defined. The directory structure looks like this.


Snap_2015.01.27 13.29.23_001


6. Run the server


There are 2 options to run this demo. The first one is to run the main in ServerRouter.java class. The Second is run via command line "mvn camel:run".  The latter way, which needs a maven plugin list at the bottom of the pom file,  is very convenient for test and development.


7. Test with a TCP client


Use telnet command as test client, connect to port 7000 with command "telnet localhost 7000". Anytime a key is pressed, a TCP message will send out to the demo server  and a reply will come back immediately.


Appendix


The netty component camel-netty is based on netty 3.x version. If a netty component based on netty 4.x is preferred, use camel-netty4 component instead.

9 comments:

  1. Hello, Thanks for post.

    I tried it and it works fine...but I wanted to switch the endpoint to be something like: from("bean:echoService).to("netty:tcp://host:port");
    If I simply switch the endpoints from your post, it is not working. Any idea?

    ReplyDelete
    Replies
    1. It doesn't make sense to switch them. let me what purpose you want to achieve. The direct reason for the failure is input parameter for bean is null, so you get NULL point exception. If you just want to forward the message to another socket server. just append to("netty:tcp//host:port....") to the route definition.

      Delete
  2. Thank you for your response..
    What I am trying to achieve is:
    1. read a file
    2. split the file into pieces
    3. send each piece separately to another netty endpoint(I dont have any control how the other end handle it thought...I will just send it, it is up to the other end how to handle it.)
    I use the below route definition...

    from("file:filepath").split().tokenize("\n", 1000).streaming().to("netty:tcp://host:port");

    .camelLock file is created, but the file is still there and never get forwarded to another socket...
    It works fine if I change the "to" endpoint other than netty...

    ReplyDelete
  3. Seems convertBodyTo(String.class) solved my issue.

    from("file:path").convertBodyTo(String.class).to("netty:tcp://host:port").

    Thank you for the help.

    ReplyDelete
  4. Hi,

    The above example explains how to start a tcp server on a particular port using camel endpoint. I have slightly different requirement. I have a server which pushes xml content on a particular port . The same way in the example localhost:7000. Now i need to write a client using netty to receive the xml content.I tried the same example and i replaced the server port and ip with my server.But i am getting bind exception.

    If i understand correctly this example is to start the server on port 7000. But i am looking for client using camel end point to consume the data from the server. Can you provide some refernce to do the same.

    Thanks in advance.

    ReplyDelete
  5. Is it possible to use Netty4+Camel as a client connecting to a server? (Instead of a server waiting for a connection) If not can you suggest how I would be able to accomplish this?

    Intuition tells me to wrap a TCPClient object in a Processor but I'm not sure if that's the best route (in the case it's not possible)

    Thank you for the tutorial.

    ReplyDelete
  6. you have not shown dependencies for StringEncoder and Decoder..

    ReplyDelete
  7. awesome tutorial helped me a lot.

    ReplyDelete
  8. If i understand correctly this example is to start the server on port 7000. But i am looking for client using camel end point to consume the data from the server. Can you provide some refernce to do the same.

    Thanks in advance.

    ReplyDelete

Powered by Blogger.

About The Author

My Photo
Has been a senior software developer, project manager for 10+ years. Dedicate himself to Alcatel-Lucent and China Telecom for delivering software solutions.

Pages

Unordered List