Wednesday, February 11, 2015

Simple unsecured Apache Proxy for Tomcat Web Socket Application

BROWSE <===> APACHE PROXY SERVER <===> TOMCAT WEB SOCKET SERVER

This is tested for the Apache Haus v 2.4.10 with Tomcat v 7 in the Windows development environment.

1. Install Apache Haus 2.4.10 in Windows Environment.
2. Install Tomcat 7 in the Windows Environment either in same machine or different machine.
3. To create websocket proxy for the Apache load following module by removing LoadModule comments in httpd.conf file:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so

For further, please refer Apache documentation at: http://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html and add following ProxyPass setup in httpd.conf configuration file

ProxyRequests Off
# If tomcat is in same machine (for websocket)
ProxyPass /websocket/echo ws://localhost:8090/websocket/echo          -> look for the trailing "/"
ProxyPassReverse /websocket/echo ws://localhost:8090/websocket/echo             -> look for the trailing "/"

# For other http request
ProxyPass /test http://localhost:8090/test
ProxyPassReverse /test http://localhost:8090/test

# If Tomcat is in different/remote machine
ProxyPass /websocket/echo ws://remote-host:8090/websocket/echo
ProxyPassReverse /websocket/echo ws://remote-host:8090/websocket/echo

# For other http request
ProxyPass /test http://remote-host:8090/test
ProxyPassReverse /test http://remote-host:8090/test

This configuration assume that Apache use port 80 and Tomcat use port 8090 and servlet context deployed in tomcat /test. Now the http or ws call to Apache will automatically redirect to corresponding Tomcat Server once it find the Proxy setting. Also take special look at the trailing "/". If you add trailing "/" for /websocket/echo/, then you need to add trailing "/" for ws://localhost:8090/websocket/echo/ too or vice-versa.

Also the ProxyPass has to be set up in specific order (take a look at the Apache documentation for this).

Here is how someone in Stack Overflow did (very useful): http://stackoverflow.com/questions/17649241/reverse-proxy-with-websocket-mod-proxy-wstunnel

4. Create simple websocket application in Tomcat as:
@ServerEndpoint("/echo")
public class EchoWebSocket {
    private Logger logger = Logger.getLogger(this.getClass().getName());
    @OnOpen
    public void onOpen(Session session) {
        logger.info("Connected .... " + session.getId());
    }
    @OnMessage
    public String onMessage(String message, Session session) {
        switch (message) {
        case "quit":
            try {
                session.close(new CloseReason(CloseCodes.NORMAL_CLOSURE,
                        "Connection is closed."));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            break;
        }
        return message;
    }
    @OnClose
    public void onClose(Session session, CloseReason closeReason) {
        logger.info(String.format("Session %s closed because of %s",
                session.getId(), closeReason));
    }
}

*Remember when deploying the Tomcat application, don't put websocket-api.jar library in the application classpath. Since this library is already included in the Tomcat library, adding this in application class-path may not work (At least for me it didn't work).

5. Add the JavaScripts HTML5 WebSocket API script as:

function wsocket() {
    var ws = null;
    var wsProtocol = (window.location.protocol === "https:" ? "wss" : "ws");
    var target =  wsProtocol + "://" + window.location.host + "/dwrdemo/echo";
    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.');
    }

    ws.onopen = function() {
        console.log("WebSocket has been opened!");
    };

    ws.onmessage = function(message) {
       console.log("WebSocket message is: " + message.data);
    };

    ws.onerror = function() {
        console.log("WebSocket connection has error!");
    }
   
    ws.onclose = function() {
        console.log("WebSocket is closed.");
    }
}


6. Add the Tomcat deployable war file in the Tomcat Server and test to make sure it is working. Once working access the page using the Apache web server.
7. The Request and response header for the websocket look something like as shown below if connection is established successfully

URL: http://localhost/websocket/echo

Request Headers:
    Accept    text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Encoding    gzip, deflate
    Accept-Language    en-US,en;q=0.5
    Cache-Control    no-cache
    Connection    keep-alive, Upgrade
    Cookie    JSESSIONID=3CD55817617CA29EF8F133721A1E1388
    Host    localhost
    Origin    http://localhost
    Pragma    no-cache
    Sec-WebSocket-Key    2cWlxhpbtE0dO1ijeaocaA==
    Sec-WebSocket-Version    13
    Upgrade    websocket
    User-Agent    Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0

Response Headers:
    Cache-Control    private
    Connection    upgrade
    Date    Fri, 06 Feb 2015 15:15:01 GMT
    Expires    Wed, 31 Dec 1969 19:00:00 EST
    Sec-WebSocket-Accept    jMl5aXuuLM8xJ35XDGAY8uncIWk=
    Server    Apache-Coyote/1.1
    Upgrade    websocket

Take a look at the Connection Upgrate, the regular http request is updated to websocket for the websocket connection. This is how the websocket work by upgrading the current protocol in the initial handshake.

Testing using JConsole as:

var ws = new WebSocket("ws://localhost/websocket/echo");
ws.readyState

No comments:

Post a Comment