18 December 2014

Clustering WebSockets on Wildfly

Brief

This is the first on a 2-part blog about how to assemble an application that is using WebSockets on WildFly in a clustered environment on Amazon EC2. There will be five main sections covered here and they can be broken down into the following:


  • Architecture set up
  • WildFly clustering configuration on EC2
  • WebSocket application on WildFly
  • Storing and ensuring high availability of data using Infinispan
  • Apache load balancer configuration

This first part will be focusing on the first three points listed above. In the second part I will detail how to set up Infinispan and configure the Apache load balancer.

Introduction


To quote the lovely people at Mozilla, 'WebSockets is an advanced technology that makes it possible to open an interactive communication session between the user's browser and a server'.  In a more technical sense, it means that it allows two-way communication between a client and a server over a single TCP connection. It is primarily designed to be used in a web application, i.e. with a browser-based front-end. However, it is possible to use it in any client-server application as well.

WebSockets are one of the new features in Java EE 7 (JSR-356), so we thought it would be a nice idea to build a simple web application that made use of WebSockets in a clustered environment.

For this example, we will use the WildFly Application Server, which is the free open source Java EE 7 platform available from JBoss. We will be running this entire demo on Amazon EC2.

Architecture set-up


The following is the set up that we intend to use for the purposes of this application.


Both of the WildFly instances and the httpd one are set up to be on their own individual virtual machines on EC2. There are three main reasons for this set up:
  1. We only need to expose the public IP address of the httpd server, thus we don't need to get a public IP for any of the WildFly instances.
  2. We can add or remove more WildFly servers to the cluster if needed and not have to worry about the public facing IP address of the application.
  3. Since there is one application running, we don't need to run the server in domain mode, and standalone is sufficient.


WildFly clustering configuration


Below is an example of how to configure JGroups for clustering on WildFly, you would have to add this into either the standalone-ha.xml or standalone-full-ha.xml


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
        <subsystem xmlns="urn:jboss:domain:jgroups:2.0" default-stack="tcp">
            <stack name="udp">
                <transport type="UDP" socket-binding="jgroups-udp"/>
                <protocol type="PING"/>
                <protocol type="MERGE3"/>
                <protocol type="FD_SOCK" socket-binding="jgroups-udp-fd"/>
                <protocol type="FD_ALL"/>
                <protocol type="VERIFY_SUSPECT"/>
                <protocol type="pbcast.NAKACK2"/>
                <protocol type="UNICAST3"/>
                <protocol type="pbcast.STABLE"/>
                <protocol type="pbcast.GMS"/>
                <protocol type="UFC"/>
                <protocol type="MFC"/>
                <protocol type="FRAG2"/>
                <protocol type="RSVP"/>
            </stack>
            <stack name="tcp">
                <transport type="TCP" socket-binding="jgroups-tcp"/>
                <protocol type="TCPPING">
                    <property name="initial_hosts">$YOUR_FIRST_IP_ADDRESS[7600],$YOUR_SECOND_IP_ADDRESS[7600]</property>
                    <property name="port_range">0</property>
                    <property name="timeout">3000</property>
                    <property name="num_initial_members">2</property>
                </protocol>
                <protocol type="MERGE2"/>
                <protocol type="FD_SOCK" socket-binding="jgroups-tcp-fd"/>
                <protocol type="FD"/>
                <protocol type="VERIFY_SUSPECT"/>
                <protocol type="pbcast.NAKACK2"/>
                <protocol type="UNICAST3"/>
                <protocol type="pbcast.STABLE"/>
                <protocol type="pbcast.GMS"/>
                <protocol type="MFC"/>
                <protocol type="FRAG2"/>
                <protocol type="RSVP"/>
            </stack>
        </subsystem>

There are a few points to note here as compared to the default configuration that ships with the WildFly zip:

  • On EC2, and other similar cloud providers, UDP multicast is disabled. Enabling multicast on a public cloud would mean a significantly higher number of messages being sent across the cloud infrastructure, resulting in a large performance hit to the service being provided. As a result, for the JGroups configuration on WildFly, we will use the TCP stack by default, and configure TCPPING.
  • To configure TCPPING, we would need to add in the IP addresses of the Wildfly instances that will be present on start-up. This way we can allow for members to join the cluster. We also specify the port to be 7600.
  • Finally, add in the number of initial members to 2.

WebSocket application


For the actual application, we will start by looking at the set up for the front-end. All that this app is doing is storing a key/value pair (both Strings) and then using that same key, we can pull the value out.

Below is a snippet of the main index.html page, which will produce two forms. These forms are used to either input a key/value pair or to simply get a value using a key.


<!-- Input form -->
<form>
    <fieldset>
        <label>Store:</label>
        <input id="putKey" type="text">
        <input id="putValue" type="text">
        <input type="submit" id="store" value="StoreMe">
    </fieldset>
</form>

<h4>Result from the submit operation:</h4>
<span id="result"></span>


<br />
<br />
<!-- Get form -->
<form>
    <fieldset>
        <label>Get:</label>
        <input id="getKey" type="text">
        <input type="submit" id="getValue" value="GetMe">
    </fieldset>
</form>

<h4>Result from the get operation:</h4>
<span id="getResult"></span>

The inputs from these forms will be processed by some JavaScript code, which does the following:
  • Create two different WebSocket objects; one for storing and one for getting. These will have different endpoints associated with them - i.e. one has the resource 'store' and the other has 'getter'.
  • Take the input from the forms, and then send that input using the WebSockets to the backend.
  • Take the output from the server, and attach that message to the HTML. (See ws.onmessage and wsGet.onmessage)


<script>
        var port = "";
        var storerUrl = 'ws://' + window.location.host + port + window.location
        .pathname + 'store';
        var ws = new WebSocket(storerUrl);

        var getterUrl = 'ws://' + window.location.host + port + window
        .location.pathname + 'getter'
        var wsGet = new WebSocket(getterUrl);

        ws.onconnect = function(e) {
          console.log("Connected up!");
        };

        ws.onerror = function(e) {
          console.log("Error somewhere: " + e);
        };

        ws.onclose = function(e) {
          console.log("Host has closed the connection");
          console.log(e);
        };

        ws.onmessage = function(e) {
          document.getElementById("result").innerHTML = e.data;
        };

        wsGet.onconnect = function(e) {
            console.log("Connected up!");
        };

        wsGet.onerror = function(e) {
          console.log("Error somewhere: " + e);
        };

        wsGet.onclose = function(e) {
          console.log("Host has closed the connection");
          console.log(e);
        };

        wsGet.onmessage = function(e) {
          console.log("Received message from WebSocket backend");
          document.getElementById("getResult").innerHTML = e.data;
        };



        document.getElementById("store").onclick = function(event){
          event.preventDefault();
          var key = document.getElementById("putKey").value;
          var value = document.getElementById("putValue").value;
          var objToSend = '{"key": ' + key + ', "value": ' + value + '}';
          ws.send(objToSend);
        };

        document.getElementById("getValue").onclick = function(event) {
            event.preventDefault();
            var key = document.getElementById("getKey").value;
            var objToGet = '{"key": ' + key + '}';
            wsGet.send(objToGet);
        };

</script>

That's all we require in order to set up the front end for this application. Again, it's important to note that this is a simple app which is demonstrating how to integrate WebSockets into your Java EE application. The next part is where we will look at how we can make use of WebSockets in our Java backend to deal with this input. First, let's look at the Storer class.




@ServerEndpoint("/store")
public class Storer {

   private static Logger storerLogger = Logger.getLogger(Storer.class.getName());

   /**
    * Method that will store some basic data using the application platform's
    * clustered caching mechanism.
    *
    * @param data - The information, as a JSON from the js/html front-end.
    * @param client - the client session
    */
   @OnMessage
   public void store(String data, Session client) {
      String returnText = null;

      // Let's get the Json object first.
      JsonParserFactory factory = JsonParserFactory.getInstance();
      JSONParser parser = factory.newJsonParser();
      Map jsonMap = parser.parseJson(data);

      if (jsonMap.containsKey("key") && jsonMap.containsKey("value")) {
         // Store the data in the cache now that we have validated it.
         String key = (String) jsonMap.get("key");
         String value = (String) jsonMap.get("value");
         // TODO: Not actually storing anything yet!

         // Creating some string returns to go back to the front-end along
         // with some logging statements.
         StringBuilder sb = new StringBuilder();
         sb.append("Well done. We now have your data!");
         storerLogger.info("Going to store key " + key + " and value " +
               value + ".");
         sb.append(" Key: ").append(key).append(" Value: ").append(value)
               .append(". ");
         sb.append(" Stored at date: ").append(buildDate()).append(".");


         returnText = sb.toString();
      } else {
         // Failed the validation check. So we are now going to log some
         // information to the server and return some information to the
         // front-end as well.

         storerLogger.info("Seems to be a problem with the input. Cannot find" +
               " the appropriate \'key\' and \'value\' string keys.");
         returnText = "Problem with the input that you sent in. Are they just" +
               " the default values?";
      }

      // Send to client.
      client.getAsyncRemote().sendText(returnText);
   }

   private String buildDate() {
      return new SimpleDateFormat("HH:mm dd/MM/yy").format(Calendar
            .getInstance().getTime());
   }

}

There are some important points to note over here:


  • The @ServerEndpoint class level annotation tells the application container, Wildfly in this case, that this class will be dealing with WebSocket messages to the endpoint '/store'. Going back a little bit, in our HTML form, when we submit the key and value that we want to store, that input will be taken by this Java class.
  • A method with the @OnMethod annotation will be called when a WebSocket sends a message to this endpoint. Any class which has a @ServerEndpoint annotation can only have one @OnMessage annotation. This method will take in a String parameter (our input) and a javax.websocket.Session object.
  • In this case, we are using a simple JSON parser available here. It will parse our input into a Map object and then we can take out the keys and values which we require (as long as they exist!).
  • After storing the key/value pair, we can then return back to the front-end.
  • We use the Session object to send a message back to the front-end using the same WebSocket connection.
  • WE ARE NOT ACTUALLY STORING ANYTHING IN THIS CLASS AS YET.

As we can see, we would need a separate class for our Getter object. This would follow a similar path except it would use a different endpoint on the @ServerEndpoint annotation, and would expect a slightly different format of input. Below is how the Getter class has been set up.


@ServerEndpoint("/getter")
public class Getter {

   static Logger getterLogger = Logger.getLogger(Getter.class.getName());

   /**
    * Method that will attempt to get a value for a given key.
    *
    * @param data - A String that is the key
    * @param client - the client session
    */
   @OnMessage
   public void getValue(String data, Session client) {
      getterLogger.info("Getting key: " + data);

      String returnText = "{No value found for key " + data + "}.";
      try {
         returnText = getValueFromCache(data);
      } catch (Exception e) {
         returnText = e.getMessage();
         getterLogger.log(Level.WARNING, e.getMessage());
         e.printStackTrace();
      }

      client.getAsyncRemote().sendText(returnText);
   }



  • The getValueFromCache() method is a dud here and doesn't do anything yet.

And that's it! 

That's all for the first part of this blog looking at how to set up a clustered WebSocket application on Wildfly running on Amazon EC2. Going forward from here, we will look at how to use Infinispan for this application to allow for the availability of our data on fail-over and also on how we can put a load balancer in front of our two Wildfly instances.

Thanks for reading! 

2 December 2014

Alternative Logging Frameworks for Application Servers: Tomcat

The final part of the blog series has finally arrived, this time covering Tomcat. Once more, we will be covering the basics on configuring it to use Log4j2 and SLF4J with Logback as the logger for a sample web application.

The previous entries in this series can be found by following these links:
The environment used for this blog was a 64-bit Linux VM, JDK Hotspot 8u25, Tomcat 5.0.15, Log4j 2.1, logback 1.1.2, slf4j 1.7.7, and NetBeans 8.0.1 as my IDE.

Under the assumption that you’ve already downloaded and installed all that you need, let’s begin…

Log4j2

We’ll configure Tomcat to use Log4j2 on a per deployment basis, just like in the tutorial for WildFly. As with when configuring WildFly, we do not need to import any additional JARs into WildFly, we can just package them with the application. NetBeans will actually do this for you, which makes this process even easier.
To keep things simple, let’s continue with the test application used in the previous blogs. For those fresh coming in, or for those who just want to start from scratch, here are the instructions:
  • Create a Server in NetBeans that maps to your Tomcat installation. 
  • Create a Java web application.
  • Create a Servlet in this new project.
  • Import the Log4j2 JARs to the project:
    • Right click on the project, and select properties
    • Go to the Libraries page
    • Select Add JAR/Folder, and import the following two files:
      • log4j-api-2.1.jar
      • log4j-core-2.1.jar
    • Click OK
  • Import the log4j package into your servlet by adding this code snippet below:
 import org.apache.logging.log4j.*;  
  • Declare and initialise a logger:
 private static Logger logger = LogManager.getLogger(TestServlet.class.getName());  
  • Add some logger messages at each log level to the processRequest method, so that they get executed when the Servlet is called:
 protected void processRequest(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException 
 {
     response.setContentType("text/html;charset=UTF-8");
     try (PrintWriter out = response.getWriter()) 
     {           
         logger.trace("Tracing!");  
         logger.debug("Debugging!");  
         logger.info("Information!");  
         logger.warn("Warnings!");  
         logger.error("Oh noes!"); 
     }
 }  
Having a try or finally block for our try clause would normally be expected, though to keep things basic we'll omit it (just don't make that excuse in your other code!).

  • Edit the index.html page that was automatically generated when you created the project so that you can click on a button to call the servlet:
 <html>
    <head>
       <title>Testing</title>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
       <form name="testForm" action="TestServlet">
          <input type="submit" value="push me!" name="testybutton" />
       </form>
    </body>
 </html>
With our test application created, let’s begin configuring Log4j2. As we are taking advantage of the Log4j configuration discovery process to find the configuration file on the classpath, the file must be called log4j2.xml. In NetBeans:
  • Expand the Web Pages folder of your test application in the project navigator pane.
  • Right click on the WEB-INF folder, and create a new folder inside it called classes.
  • Right click on this new classes folder, expand the New menu, and select XML Document.
  • Call it log4j2, and just create it as a well-formed document.
  • Fill it out as follows (replacing the fileName value with your own file path and file name):
 <?xml version="1.0" encoding="UTF-8"?>  
 <Configuration status="WARN">  
    <Appenders>  
       <File name="FileLogger" fileName="/home/andrew/tomcat.log">  
           <PatternLayout pattern="%d{HH:mm} [%t] %-5level %logger{36} - %msg%n"/>  
       </File> 
       <Console name="ConsoleLogger" target="SYSTEM_OUT"> 
           <PatternLayout pattern="%d{HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
       </Console>
    </Appenders>  
    <Loggers>  
       <Root level="trace">  
           <AppenderRef ref="FileLogger"/>
           <AppenderRef ref="ConsoleLogger"/>  
       </Root>  
    </Loggers>  
 </Configuration>  
This will log the messages we specified in the servlet to a custom file, and to the default Tomcat out log, catalina.out. To help differentiate them, the messages logged to the console will log with the hour, minutes, and seconds, whereas the messages logged to our own file will only have the hour and minutes.

Testing

To test that everything is working as it should be, let’s give it a test run:
  • Clean and build the project, this will add the two JARs needed by the logger (log4j-api-2.1.jar and log4j-core-2.1.jar) to $project_install_location/build/web/WEB-INF/lib directory, so that Tomcat can use them.
  • Right click on the project, and deploy it to Tomcat.
  • Click on the play button, and your browser should load with the application.
  • Click the button, and then check in your log file and in the catalina.out log file (this can be found in $tomcat_install_location/logs), you should see your logger messages.
Take note: if you started Tomcat through NetBeans, then the log messages will not be output to the catalina.out file, instead being directed to the console output in NetBeans.

Logback

Fortuitously, Logback can be configured to work with Tomcat in almost exactly the same way as Log4j2, the only difference being the code syntax. I’ll go through the whole process again for those of you who are just jumping to this bit:
  • Create a Server in NetBeans that maps to your Tomcat installation.
  • Create a Java Web application.
  • Create a Servlet in this new project.
  • Import the Logback and SLF4J JARs into the project:
    • Right click on the project, and select properties
    • Go to the Libraries page
    • Select Add JAR/Folder, and import the following files:
      • slf4j-api-1.7.7.jar
      • logback-classic-1.1.2.jar
      • logback-core-1.1.2
    • Click OK
  • Import the following SLF4J packages into your servlet:
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
  • Declare and initialise a logger:
 private static Logger logger = LoggerFactory.getLogger(TestServlet.class.getName());
  • Log some messages at various levels in the processRequest method, such that they get triggered when the Servlet is called:
 protected void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException 
 {
    response.setContentType("text/html;charset=UTF-8");
    try (PrintWriter out = response.getWriter()) 
    {
       logger.trace("Tracing!");  
       logger.debug("Debugging!");  
       logger.info("Information!");  
       logger.warn("Warnings!");  
       logger.error("Oh dears!");
    }
 }
Again, we should really put a catch or finally block for the auto-generated try, but given that we're keeping this as basic as possible we'll omit it.

As before, let’s edit the index.html page that was automatically generated for us so that you can click on a button to call the servlet:
 <html>
    <head>
        <title>Testing</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <form name="testForm" action="TestServlet">
            <input type="submit" value="push me!" name="testybutton" />
        </form>
    </body>
 </html>
Next, let’s create a logback configuration file that prints out our logger messages to a custom file, and to the catalina.out file. In NetBeans:
  • Expand the Web Pages folder of your test application in the project navigator pane.
  • Right click on the WEB-INF folder, and create a new folder inside it called classes.
  • Right click on this new folder, expand the New menu, and select XML Document.
  • Call it logback, and create it as a well-formed document; we don’t need any schema or DTD constraints for this.
  • Populate it with this (replacing /home/andrew/tomcat.log with your own file path and file name):
 <?xml version="1.0" encoding="UTF-8"?>  
 <configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>/home/andrew/tomcat.log</file>
        <append>true</append>
        <encoder>
            <pattern>%d{HH:mm} [%thread] %-5level %logger{52} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <root level="TRACE">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
 </configuration>
Done! The only thing left to do is, build, deploy, and test the application.

Testing

Follow these instructions to build, deploy, and test the application:
  • Clean and build the project, this will add the JARs needed by the logger (logback-classic-1.1.2.jar, logback-core-1.1.2.jar, and slf4j-api-1.7.7.jar) to the $project_install_location/build/web/WEB-INF/lib directory, so that they deploy to Tomcat with the application.
  • Right click on the project, and deploy it to Tomcat.
  • Click on the play button, and your browser should load with the application.
  • Click the button, and then check in the log file you specified in the Logback configuration, and in the catalina.out log file (this can be found in $tomcat_install_location/logs), you should see your logger messages.
As I noted before, if you started Tomcat through NetBeans, then the log messages destined for the catalina.out file will instead be directed to the console output in NetBeans.

Final Thoughts

This is the last in this series, having covered GlassFish, WebLogic, WildFly, and now Tomcat; the “big four” Java EE application servers. I hope that you've found these tutorials helpful in getting started with using alternative logging frameworks with application servers, they do provide some benefits after all!

Good luck from here!

24 November 2014

DOAG 2014 Impressions

I am writing this blog from a coffee shop in Nuremberg, having just attended DOAG 2014 at the Nuremberg Conference Center. This was my second time talking at the DOAG conference and it seemed even busier than last time. DOAG is the German Oracle User Group, and the crowd at its annual conference is a wide mix of software developers, product managers and sales from a wide range of companies using the whole spectrum of Oracle products.

As last year, the sessions were mostly in German, and while my German is good enough to have a simple conversation with someone when we are both trying to keep things simple and easy to understand, it isn't up to the level required to follow a complex technical session (I did try, attending an interesting looking session on Identity Propagation in Oracle Fusion Middleware, but I only managed to follow about 10% of what was said). The number of english language sessions in the middleware track was limited (just 12) and many of these clashed with each other, which was a shame. However the sessions I did manage to attend were interesting.

There was a definite focus on Oracle SOA Suite, BPM and OSB talks this time around, with many of them being roadmap outlines presented by the Oracle product management teams (unfortunately heavily caveated with Oracle's safe harbour statement preventing me discussing the plans here). There were a few interesting how-to talks, one on how to use the user messaging service in SOA suite to notify users of items of interest, and another on integrating Oracle Event Processing and SOA suite. OEP is a product that I can see becoming more widely used as organisations seek to identify and act on patterns in the event streams generated by their SOA Suite applications.

My presentation was on the Thursday (luckily in the afternoon, giving people time to recover from the community party the night before) and was well attended, with two thirds of the seats taken. The topic was using WLST to create WebLogic domains (see slides below), and the audience was a mix of people who have already started down this road and wanted advice on best practices, through to people who had no experience of WLST. I had some interesting conversations with people after the presentation, and it was good to see so many people who want to take devops to its logical conclusion and include domain builds in their continuous build and integration suites.

I hope to be back for DOAG 2015, and already have some ideas for presentations that people may find interesting. If DOAG can continue to expand the english language agenda (and schedule the english language sessions in a track so that they don't clash) it has the potential to draw people from all around Europe.