Monday, October 20, 2014

WebSockets in Java. Tomcat and Apache Configuration.




Recently I introduced WebSockets at my day job's webapp. I was thrilled by it's capabilities and simplicity. This tutorial covers writing back-end code, front-end code and configuration changes might be needed to use websockets with Tomcat and Apache webserver.

WebSockets


 The Wikipedia definitions is "WebSocket is a protocol providing full-duplex communications channels over a single TCP connection". This simple interface of TCP connection in javascript enables to push data from server to client as and when data is available instead of clients polling for it. For example if you want to tail the server logs in the browser, you can read log files, buffer logs and push log data to browser.  For reading appended data to log file you can use Apache Tailer.


Server Side

1. We need to implement an Endpoint on the server side. This endpoint class will be invoked when a connection is opened or closed from the client. The endpoint class can be marked by annotation or by extending Endpoint. Following is a sample

public class SimpleEndpoint extends Endpoint {
   public void onOpen(Session session, EndpointConfig endpointConfig) {
         RemoteEndpoint.Basic remoteEndpointBasic = session.getBasicRemote();
         //Save endpoint remoteEndpointBasic to be used later for pushing data.
  }
  public void onClose(Session session, CloseReason closeReason) {
        super.onClose(session, closeReason);
  }

  public void onError(Session session, Throwable throwable) {
        super.onError(session, throwable);
  }
}

2.  above endpoint to be invoked, we need to provide Endpoint and invocation path mapping.  This is done by adding a class in the classpath that implementing ServerApplicationConfig.


public class SimpleConfig implements ServerApplicationConfig {
    public Set<ServerEndpointConfig> getEndpointConfigs( Set<Class<? extends Endpoint>> scanned) {
        Set<ServerEndpointConfig> result = new HashSet<ServerEndpointConfig>();
        if (scanned.contains(SimpleEndpoint.class)) {
            //Adding SimpleEndpoint
            result.add(ServerEndpointConfig.Builder.create( SimpleEndpoint.class, "/websocket").build());
        }
        return result;
    }
    public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) {
        Set<Class<?>> results = new HashSet<Class<?>>();
        return results;
    }
}


3. Data from server can be pushed using RemoteEndpoint.Basic object that we saved in step 1.

RemoteEndpoint.Basic r = getMyEndpoint();
try {
r.sendText("Your Text Data Goes Here");
}
catch(IOException e){
//discard endpoint
}

 4. For receiving messages from the client we need to create a message handler as below

SimpleMessageHandler implements MessageHandler.Whole<String> {
        SimpleMessageHandler(RemoteEndpoint.Basic remoteEndpointBasic) {
        }
        public void onMessage(String message) {
//I got this --> message
        }
    }
and register message handler with the websocket session.
    public void onOpen(Session session, EndpointConfig endpointConfig) {
        session.addMessageHandler(new SimpleMessageHandler(remoteEndpointBasic));
    }



Client Side

1. Javascript part is made very easy. We just need to create WebSocket object using target url, as follows

    var target = getTargetUrl();
    var ws = null;
    if ('WebSocket' in window) {
        ws = new WebSocket(target);
    }
    else if ('MozWebSocket' in window) {
        ws = new MozWebSocket(target);
    }
    else {
        alert('WebSocket is not supported by this browser.');
        return;
    }

2. Callback methods can be registered.

    ws.onopen = function () {
        //Connection opened with server
    };
    ws.onmessage = function (event) {
//message received from server, message data is in
//event.data
    };
    ws.onclose = function (event) {
        //Connection Closed with server
    };

Tomcat

Tomcat 7 onward websockets are supported. Just make sure that a http based Connector is configured. An AJP connector will not do because AJP doesn't support websockets protocol. Http connector can be configured in server.xml file as follows


<Connector port="8009" address="0.0.0.0" protocol="HTTP/1.1" connectionTimeout="20000" maxPostSize="10485760" redirectPort="8443" URIEncoding="UTF-8"/>


Apache Web Server

Apache Web Server (Httpd) added support for websockets handling in 2.4 version. For supporting websocket you need to load and configure proxy modules.  In the httpd.conf file load proxy modules using following configuration


LoadModule proxy_module modules/mod_proxy.so
LoadModule  proxy_wstunnel_module modules/mod_proxy_wstunnel.so
<IfModule mod_proxy_ajp.c>
Include conf/my-mod_proxy.conf
</IfModule> 

in my-mod_proxy.conf file configure proxy using following commands:


<Proxy /myapp/*>
    Require all granted
</Proxy>
#below is to configure reverse proxy
ProxyRequests Off
#provide mapping between client url and tomcat accepted url.
ProxyPass /myapp/websocket ws://127.0.0.1:8009/myapp/websocket 

Open Question

I was running tomcat on 8009 port and apache on port 80. Before using websockets my tomcat was running AJP based connector. For using websockets I changed my connector for HTTP, but now with http based connector tomcat got exposed. I found no good way to limit Tomcat Connector to Apache and posted this question on StackOverflow. But so far I have not received any satisfying answer.
  

Thursday, August 11, 2011

Hello! Blog World.

"There are 10 kind of people in this world: those who know binary and those who don't. "
-Anonymous.

Hi, I am an enthusiastic programmer. This is my first blog. I hope to regularly come here and share some interesting things about programming and etc.