The WebSockets protocol and API is an emerging standard to provide better bidirectional communication between a browser (or other web client) and a server.  It is intended to eventually replace the comet techniques like long polling.   Jetty has supported the various websocket drafts in the 7.x and 8.x releases and this blog tells you how to get started with websockets.

You don’t want to do this!

This blog will show you how to use websockets from the lowest levels, but I would not advise that any application programmer should follow these examples to build and application.   WebSockets is not a silver bullet and on it’s own it will never be simple to use for non trivial applications (see Is WebSocket Chat Simpler?), so my recommendation is that application programmers look toward frameworks like cometd, that private a higher level of abstraction, hide the technicalities and allow either comet long polling or websockets to be used transparently.

So instead this blog is aimed at framework developers who want to use websockets in their own frameworks and application developers who can’t stand not knowing what is under the hood.

Test Client and Server

The simplest way to get started is to download a jetty aggregate jar that comes complete with a test websocket client and server.  You can do this with a browser of with the following command line wgets:

wget -O jetty-all.jar --user-agent=demo
  http://repo2.maven.org/maven2/org/eclipse/jetty/aggregate/jetty-all/7.4.0.v20110414/jetty-all-7.4.0.v20110414.jar
wget --user-agent=demo
  http://repo2.maven.org/maven2/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar

To run a simple test server (use –help to see more options):

java -cp jetty-all.jar:servlet-api-2.5.jar
  org.eclipse.jetty.websocket.TestServer
  --port 8080
  --docroot .
  --verbose

You can test the server with the test client (use –help to see more options):

java -cp jetty-all.jar:servlet-api-2.5.jar
  org.eclipse.jetty.websocket.TestClient
  --port 8080
  --protocol echo

The output from the test client is similar to ping and you can use the options discovered by –help to try out different types of tests, including fragmentation and aggregation of websocket frames

Using a Browser

Using a java client is not much use, unless you want to write a desktop application that uses websocket (a viable use).  But most users of websocket will want to use the browser as a client.  So point your browser at the TestServer at http://localhost:8080.

The Websocket TestServer also runs a HTTP file server at the directory given by –docroot, so in this case you should see in the browser a listing of the directory in which you ran the test server.

To turn the browser into a websocket client, we will need to server some HTML and javascript that will execute in the browser and talk back to the server using websockets.  So create the file index.html in the same directory you ran the server from and put into it the following contents which you can download from here. This index file contains the HTML, CSS and javascript for a basic chat room.

You should now be able to point your browser(s) at the test server and see a chat room and join it.  If your browser does not support websockets, you’ll be given a warning.

How does the Client work?

The initial HTML view has a prompt for a user name.  When a name is entered the join method is called, which creates the websocket to the server.  The URI for the websocket is derived from the documents location and call back functions are registered for open, message and close events.   There org.ietf.websocket.test-echo-broadcast sub protocol is specified as this echos all received messages to all other broadcast connections, giving use the semantic needed for a chat room:

join: function(name) {
  this._username=name;
  var location = document.location.toString().replace('http://','ws://').replace('https://','wss://');
  this._ws=new WebSocket(location,"org.ietf.websocket.test-echo-broadcast");
  this._ws.onopen=this._onopen;
  this._ws.onmessage=this._onmessage;
  this._ws.onclose=this._onclose;
},

When the websocket is successful at connecting to the server, it calls the onopen callback, which we have implemented to change the appearance of the chat room to prompt for a chat message.  It also sends a message saying the user has joined the room:

_onopen: function(){
  $('join').className='hidden';
  $('joined').className='';
  $('phrase').focus();
  room._send(room._username,'has joined!');
},

Sending of a message is done by simply formatting a string as “username:chat text” and calling the websocket send method:

_send: function(user,message){
  user=user.replace(':','_');
  if (this._ws)
    this._ws.send(user+':'+message);
},
chat: function(text) {
  if (text != null && text.length>0 )
     room._send(room._username,text);
},

When the browser receives a websocket message over the connection the onmessage callback is called with a message object. Our implementation looks for  the username and colon, strips out any markup and then appends the message to the chat room:

_onmessage: function(m) {
  if (m.data){
    var c=m.data.indexOf(':');
    var from=m.data.substring(0,c).replace('<','<').replace('>','>');
    var text=m.data.substring(c+1).replace('<','<').replace('>','>');
    var chat=$('chat');
    var spanFrom = document.createElement('span');
    spanFrom.className='from';
    spanFrom.innerHTML=from+': ';
    var spanText = document.createElement('span');
    spanText.className='text';
    spanText.innerHTML=text;
    var lineBreak = document.createElement('br');
    chat.appendChild(spanFrom);
    chat.appendChild(spanText);
    chat.appendChild(lineBreak);
    chat.scrollTop = chat.scrollHeight - chat.clientHeight;
  }
},

If the server closes the connection, or if the browser times it out, then the onclose callback is called.  This simply nulls out the chat room and reverts to the starting position:

_onclose: function(m) {
  this._ws=null;
  $('join').className='';
  $('joined').className='hidden';
  $('username').focus();
  $('chat').innerHTML='';
}

How Does the Server Work?

The server side code for  this chat room is using an embedded Jetty server and is written against the jetty websocket APIs that are not part of the websocket standard.  There is not yet even a proposed standard for serverside websocket APIs, but it is a topic for consideration with the servlet 3.1 JSR.

The test server is an extension of an embedded Jetty server, and the constructor adds a connector at the required port, creates a WebSocketHandler and a ResourceHandler and chains them together:

public TestServer(int port)
{
    _connector = new SelectChannelConnector();
    _connector.setPort(port);
    addConnector(_connector);
    _wsHandler = new WebSocketHandler()
    {
        public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
        {
            ...
            return _websocket;
        }
    };
    setHandler(_wsHandler);
    _rHandler=new ResourceHandler();
    _rHandler.setDirectoriesListed(true);
    _rHandler.setResourceBase(_docroot);
    _wsHandler.setHandler(_rHandler);
}

The resource handler is responsible for serving the static content like HTML and javascript.  The WebSocketHandler looks for WebSocket handshake request and handles them by calling the doWebSocketConnect method, which we have extended to create a WebSocket depending on the sub protocol passed:

_wsHandler = new WebSocketHandler()
{
    public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
    {
        if ("org.ietf.websocket.test-echo".equals(protocol) || "echo".equals(protocol) || "lws-mirror-protocol".equals(protocol))
            _websocket = new TestEchoWebSocket();
        else if ("org.ietf.websocket.test-echo-broadcast".equals(protocol))
            _websocket = new TestEchoBroadcastWebSocket();
        else if ("org.ietf.websocket.test-echo-assemble".equals(protocol))
            _websocket = new TestEchoAssembleWebSocket();
        else if ("org.ietf.websocket.test-echo-fragment".equals(protocol))
            _websocket = new TestEchoFragmentWebSocket();
        else if (protocol==null)
            _websocket = new TestWebSocket();
        return _websocket;
    }
};

Below is a simplification of the test WebSocket from the test server, that excludes the shared code for the other protocols supported. Like the javascript API, there is an onOpen,onClose and onMessage callback. The onOpen callback is passed in a Connection instance that is used to send messages. The implementation of onOpen adds the websocket to a collection of all known websockets, and onClose is used to remove the websocket. The implementation of onMessage is to simply iterate through that collection and to send the received message to each websocket:

ConcurrentLinkedQueue _broadcast =
    new ConcurrentLinkedQueue();
class TestEchoBroadcastWebSocket implements WebSocket.OnTextMessage
{
    protected Connection _connection;
    public void onOpen(Connection connection)
    {
        _connection=connection;
        _broadcast.add(this);
    }
    public void onClose(int code,String message)
    {
        _broadcast.remove(this);
    }
    public void onMessage(final String data)
    {
        for (TestEchoBroadcastWebSocket ws : _broadcast)
        {
            try
            {
                ws._connection.sendMessage(data);
            }
            catch (IOException e)
            {
                _broadcast.remove(ws);
                e.printStackTrace();
            }
        }
    }
}

Don’t do it this way!

Now you know the basics of how websockets works, I repeat my warning that you should not do it this way – unless you are a framework developer.   Even then, you are probably going to want to use the WebSocketServlet and a non embedded jetty, but the basic concepts are the same. Note the strength of the jetty solution is that it terminates both WebSocket connections and HTTP requests in the same environment, so that mixed frameworks and applications are easy to create.

Application developers should really look to a framework like cometd rather than directly coding to websockets themselves.  It is not that the mechanics of websockets are hard, just that they don’t solve all of the problems that you will encounter in a real world comet application.

 


14 Comments

Sandeep · 20/04/2011 at 12:16

Thanks for this great article
Extreme Java

Bogdan Flueras · 24/05/2011 at 13:51

Hi Greg,
1) Which version of the WebSocket protocol draft does your latest version of Jetty (8.0.0.M2)support? (As I am confused which drafts are used [ietf-hybi and hixie] on both server side and browser side)
2) Provided that I switch to other version of Jetty, how can I “see it” in the source code the WebSocket draft protocol? Is it hardcoded somewhere?
3) Which is the most stable version of Jetty with support for WebSockets? [the last one? :)]
4) Why there is no final release for Jetty server starting from version 7?
Thank you

    gregw · 24/05/2011 at 22:20

    1) 8M2 has -76 and -00 support for it, which is not that useful any more as browsers switch to -07. 8M3 is out any day now and has -07 support.
    2) Jetty tries to support multiple versions of ws, so the latest supports -76,-00, -01,-06 and -07. The spec is resistant to declaring versions because the idea is that eventually there will be no versions…. not much help during this draft phase.
    3) 7.4.1 (or 7.4.2 later this week)
    4) The 7.4.x versions of jetty are the current stable series, so they are “finals”. There is not yet a final of jetty-8 as we are working through some IP issues with the eclipse foundation for JSP2.2, but we think we have solved those, so M3 out in days and an 8 final in weeks (I hope).

      Ken Harrison · 22/08/2011 at 18:08

      Greg,
      What versions of the websockets protocol does 8.0.0.RC0. I appears to be -00, -06, -10 are -76 and -07 supported as well?
      Thanks,
      Ken

        gregw · 22/08/2011 at 23:51

        Jetty (7 and 8 ) support -00 (which is equivalent to -76) and -6 through -10 (including -07).
        cheers

Sewillby · 02/08/2011 at 09:12

Hi,
I’m trying to work myself into the topic of WebSockets but currently I’m failing at the simplest examples. :/ In case of this article I can’t run the simple test server after downloading the jars..
java is exiting with java.lang.ClassNotFoundException: org.eclipse.jetty.websocket.TestServer
For a dirty workaround I downloaded jetty-websocket-7.4.0.RC0.jar from grebcode.com and put it into java/lib directory but this didn’t work ..
I think this should be a very stupid problem for a real web developer. I’m just starting in this area, while usually developing non-java desktop applications..

gregw · 14/08/2011 at 23:54

Sewillby,
best to ask such support questions on the mailing lists or in #jetty channel of IRC

Lale · 02/01/2012 at 18:21

Good.
Actually, I like Websocket more than other methods . I think it is even more convenient than comet or async in servlet3.0.

AL · 11/07/2012 at 23:40

Will this work on existing web server? How can i integrate this functionality with existing jetty web server?

gregw · 26/07/2012 at 07:01

AL,
if you are running a recent Jetty webserver, then websockets are available. It is just a matter of turning them on and writing an application. Or you can just use cometd to hide the details for you.

網站製作學習誌 » [Web] 連結分享 · 30/04/2011 at 00:56

[…] Getting Started With Websockets […]

HTML5 WebSockets and Real-Time Tutorials and Resources - WebsitesMadeRight.com · 01/05/2011 at 06:01

[…] Getting Started With Websockets (Webtide Blogs | Apr 17, 2011) […]

Javascript by vbrouard - Pearltrees · 10/01/2012 at 04:46

[…] To turn the browser into a websocket client, we will need to server some HTML and javascript that will execute in the browser and talk back to the server using websockets. So create the file index.html in the same directory you ran the server from and put into it the following contents which you can download from here . This index file contains the HTML, CSS and javascript for a basic chat room. Getting Started With Websockets | Webtide Blogs […]

What browsers support HTML5 WebSocket API? [closed] | Everyday I'm coding · 12/03/2013 at 11:56

[…] Jetty 7.0 supports it (very easy to use) V 7.5 supports RFC6455 […]

Comments are closed.