On the client side, you need to include RMIWebSocket.js (CC-BY license) in the HTML like this:
<script src="RMIWebSocket.js"></script>
You can then create a callback object that can be invoked from the server. Here is an example from Samples/src/main/webapp/index.jsp:
callback = { updateF: function(str) { fref.value = str; unsetErr(); }, updateC: function(str) { cref.value = str; unsetErr(); }, setErrF: function(str) { fref.style.background = "#FF6600"; }, setErrC: function(str) { cref.style.background = "#FF6600"; }, onsocketerror: function(str) { alert("Failed to connect or lost web socket"); }, onrmierror: function(str) { alert("RMI error: " + str); } };
The methods onsocketerror
and onrmierror
are optional methods that can be used to handle exceptions during communication. The other methods, viz. updateF
, updateC
, setErrF
and setErrC
, can be invoked from the server once the websocket is established.
The URI for the websocket endpoint is of the form 'ws://server:port/path?get-params. More information is available at http://en.wikipedia.org/wiki/WebSocket. In the above example, the URI is computed with simple string operations on the client's HTTP URL.
// Construct the WebSocket URI httpUri = location.href; wsUri = "ws://" + httpUri.substring(httpUri.indexOf("://") + 3, httpUri.lastIndexOf("/")) + "/TempConversion";
Once the URI and callback object are available, the RMIWebSocket object can be created as follows:
rws = new RMIWebSocket(wsUri, callback);
This object supports call
with one or more arguments where the first argument refers to a server-side method and the following arguments are parameters to that method. In the above example, the HTML page includes Javascript functions to invoke two method (onTempChangeC
and onTempChangeF
), as follows:
<form id="ftoc"> <table> <tr><td>Farenheit <td> <input name="fval" onchange="submitf()"></input> </tr></td> <tr><td>Celcius <td> <input name="cval" onchange="submitc()"></input> </tr></td> </table> </form> ... function submitf() { rws.call('onTempChangeF', fref.value); } function submitc() { rws.call('onTempChangeC', cref.value); }
The next section describes the server-side usage for this example.
On the server side, you need to include rmi-websocket-<ver>.jar (Apache 2 license) in your Jetty web application. Here is the example servlet from Samples/src/main/java/com/lambdazen/websocket/sample/:
package com.lambdazen.websocket.sample; import javax.servlet.http.HttpServletRequest; import com.lambdazen.websocket.AbstractRMIWebSocketServlet; import com.lambdazen.websocket.IRMIWebSocket; import com.lambdazen.websocket.IRMIWebSocketListener; public class TempConversionServlet extends AbstractRMIWebSocketServlet { public IRMIWebSocketListener createListener(IRMIWebSocket rws) { return new TempConversionService(rws); } public boolean checkOrigin(HttpServletRequest request, String origin) { // Allow all return true; } }
AbstractRMIWebSocketServlet
extends javax.servlet.http.HttpServlet
and implements a websocket endpoint using Jetty's WebSocket Factory. The implementation is similar to the example from Websocket Example: Server, Client and LoadTest. The createListener
method is invoked when a browser connects to the servlet's URI using web sockets.
In the above example, TempConversionService
is the server-side equivalent of the callback
object in the client-side. The handle to IRMIWebSocket
is useful to call methods on the browser.
public class TempConversionService implements IRMIWebSocketListener { IRMIWebSocket rws; ... public TempConversionService(IRMIWebSocket rws) { this.rws = rws; ... }
This class implements IRMIWebSocketListener which provides three methods for cleanup and error handling:
public void onClose() { ... } public void onDeserializationError(Exception e, String msg) { ... } public void onError(RMIWebSocketException e) { ... }
The method onClose()
is called when the websocket is closed from the client. The other two methods are invoked when there are errors in deserialization or communication (resp).
The other public methods implemented by the TempConversionService
can be invoked by the rws.call(...)
statements in the client. The onTempChangeF
and onTempChangeC
methods are implemented as follows:
public void onTempChangeF(String val) { onTempChange(true, val); } public void onTempChangeC(String val) { onTempChange(false, val); } private void onTempChange(boolean isFarenheit, String val) { try { double temp; try { temp = Double.valueOf(val); } catch (NumberFormatException e) { // Mark field as erroneous rws.call(isFarenheit ? "setErrF" : "setErrC"); return; } double otherTemp = isFarenheit ? (temp - 32) * 5 / 9 : temp * 9/5 + 32; rws.call(isFarenheit ? "updateC" : "updateF", formatter.format(otherTemp)); } catch (RMIWebSocketException e) { // Some error reporting System.err.println("RMIWebSocket call error"); e.printStackTrace(); } }
You can see that the calls to the onTempChangeF/C automatically invoke the various methods in the Javascript callback object using rws.call
invocations.
In summary, you can use RMIWebSocket to invoke methods from a browser to a Jetty Web Server or vice-versa, by following these steps:
1. Write a service class by implementing IRMIWebSocketListener
with additional public methods that you want to expose to the client (Javascript running on a browser)
2. Write and configure a servlet by extending AbstractRMIWebSocketServlet
. The createListener
method in this class must return the service class.
3. Use the IRMIWebSocket
implementation passed in createListener
to invoke calls on the client side.
4. Include RMIWebSocket.js
in the HTML pages returned to the client.
5. Create a Javascript object for callbacks from the server to the browser.
6. Instantiate a Javascript RMIWebSocket
object using the websocket URI corresponding to the servlet and a callback object.
7. Use the above object to make calls to the server.