Caucho Technology

hello, world websocket in resin


A "hello, world" WebSocket servlet demonstrating the Resin WebSocket API.

Demo

WebSocket Overview

WebSocket is a new browser capability being developed for HTML 5 browsers, enabling fully interactive applications. With WebSockets, both the browser and the server can send asynchronous messages over a single TCP socket, without resorting to long polling or comet.

A WebSocket is a bidirectional message stream between the client and the server. The socket starts out as a HTTP connection and then "Upgrades" to a TCP socket after a HTTP handshake. After the handshake, either side can send data.

While this tutorial shows the low-level Resin API on top of WebSockets, it's expected that applications will build their own protocols on top of WebSockets. So application code will typically be written to the application protocols, not the low-level text and binary stream. Some possible examples are given later in the tutorial.

Resin's WebSocket API follows the Servlet API's stream model, using InputStream/OutputStream for binary messages and a Reader/PrintWriter for text messages. HTTP browsers will use text messages, while custom clients like phone/pad applications may use binary messages for efficiency.

Tutorial Description

Since the tutorial is a hello, world, the JavaScript just does the following:

  1. Connects to the Resin WebSocket servlet
  2. Sends a "hello" query to the servlet
  3. Sends a "server" query to the servlet
  4. Displays any received messages from the servlet

Correspondingly, the server does the following:

  1. Checks the handshake for a valid "hello" protocol.
  2. Dispatches a HelloListener to handle the web socket request.
  3. Handles messages throught he HelloListener until the connection ends.

Files in this tutorial

FILEDESCRIPTION
websocket.phpwebsocket HTML page and JavaScript
WEB-INF/classes/example/HelloWebSocketServlet.javaservlet to upgrade HTTP handshake to websocket
WEB-INF/classes/example/WebSocketListener.javawebsocket listener for client messages
WEB-INF/resin-web.xmlresin-web.xml configuration

WebSocket Servlet

Resin's WebSocket support is designed to be as similar to the Servlet stream model as possible, and to follow the 3.0 Async API model where possible. Because the client and server APIs are symmetrical, the main API classes (WebSocketListener and WebSocketContext) have no servlet dependencies.

The WebSocket API is divided into three major tasks:

  • WebSocketServletRequest - HTTP handshake to establish a socket.
  • WebSocketContext - API to send messages.
  • WebSocketListener - callback API to receive messages.

WebSocket handshake - starting the connection

To upgrade a HTTP socket to WebSocket, the ServletRequest is cast to a WebSocketServletRequest (implemented by Resin), and then websockets is started with astartWebSocketcall.

(TheWebSocketServletRequestAPI is temporary until the next Servlet specification integrates thestartWebSocketmethod directly.)

Example: Upgrading to WebSocket
import com.caucho.servlet.WebSocketServletRequest;
import com.caucho.servlet.WebSocketListener;

...
public class MyServlet extends HttpServlet {

  public void service(HttpServletRequest req, HttpServletResponse res)
    throws IOException, ServletException
  {
    String protocol = req.getHeader("Sec-WebSocket-Protocol");

    WebSocketListener listener;

    if ("my-protocol".equals(protocol)) {
      listener = new MyListener();
      
      res.setHeader("Sec-WebSocket-Protocol", "my-protocol");
    }
    else {
      res.sendError(404);
      return;
    }
    
    WebSocketServletRequest wsReq = (WebSocketServletRequest) req;

    wsReq.startWebSocket(listener);
  }
}

WebSocketContext - sending messages

TheWebSocketContextis used to send messages. Applications will need to synchronize on theWebSocketContextwhen sending messages, because WebSockets is designed for multithreaded applications, and because theWebSocketContextis not thread safe.

A message stream starts withstartTextMessageorstartBinaryMessageand is then used like a normalPrintWriterorOutputStream. Closing the stream finishes the message. A new message cannot be started until the first message is completed by callingclose().

Example: sending a message
public void sendHello(WebSocketContext webSocket)
  throws IOException
{  
  PrintWriter out = webSocket.startTextMessage();
  out.println("hello");
  out.println("world");
  out.close();
}

WebSocketListener - receiving messages

The WebSocketListener is the heart of the server-side implementation of websockets. It is a single-threaded listener for client events.

When a new packet is available, Resin will call theonReadmethod, expecting the listener to read data from the client. While theonReadis processing, Resin will not callonReadagain until the first one has completed processing.

In this example, the handler reads a WebSocket text packet and sends a response.

TheReaderandPrintWriterfrom theWebSocketContextare not thread safe, so it's important for the server to synchronize writes so packets don't get jumbled up.

Example: EchoHandler.java
package example;

import com.caucho.websocket.WebSocketContext;
import com.caucho.websocket.AbstractWebSocketListener;

public class EchoHandler extends AbstractWebSocketListener
{
  ...

  @Override
  public void onReadText(WebSocketContext context, Reader is)
    throws IOException
  {
    PrintWriter out = context.startTextMessage();

    int ch;

    while ((ch = is.read()) >= 0) {
      out.print((char) ch);
    }

    out.close();
    is.close();
  }
}

WebSocketListener

Resin's WebSocketListener is the primary interface for receiving messages. The listener serializes messages: following messages will be blocked until the callback finishes processing the current one. Since only a single message is read at a time, the listener is single-threaded like a servlet.

TheonStartcallback is called when the initial handshake completes, letting the server send messages without waiting for a client response.

onCloseis called when the peer gracefully closes the connection. In other words, an expected close.

onDisconnectis called when the socket is shut down. So a graceful close will haveonClosefollowed byonDisconnect, while a dropped connection will only have anonDisconnect.

WebSocketListener.java
package com.caucho.servlet;

public interface WebSocketListener
{
  public void onStart(WebSocketContext context)
    throws IOException;

  public void onReadBinary(WebSocketContext context, InputStream is)
    throws IOException;

  public void onReadText(WebSocketContext context, Reader is)
    throws IOException;

  public void onClose(WebSocketContext context)
    throws IOException;

  public void onDisconnect(WebSocketContext context)
    throws IOException;

  public void onTimeout(WebSocketContext context)
    throws IOException;
}

WebSocketContext

The WebSocket context gives access to the WebSocket streams, as well as allowing setting of the socket timeout, and closing the connection.

WebSocketContext.java
package com.caucho.servlet;

public interface WebSocketContext
{
  public OutputStream startBinaryMessage() throws IOException;

  public PrintWriter startTextMessage() throws IOException;

  public void setTimeout(long timeout);

  public long getTimeout();

  public void close();
  
  public void disconnect();
}

WebSocket JavaScript

Connecting to the WebSocket in JavaScript

Example: WebSocket connect in JavaScript
<?php
  $url = "ws://localhost:8080/example/websocket";
?>

<script language='javascript'>

function onopen(event) { ... }
function onmessage(event) { ... }
function onclose(event) { ... }

ws = new WebSocket("<?= $url ?>");
wsopen.ws = ws;
ws.onopen = wsopen;
ws.onmessage = wsmessage;
ws.onclose = wsclose;

</script>

Receiving WebSocket data in JavaScript

Example: receive WebSocket message
<script language='javascript'>

function wsmessage(event)
{
  data = event.data;

  alert("Received: [" + data + "]");
}

</script>

Sending WebSocket data in JavaScript

Example: send WebSocket message
<script language='javascript'>

function wsopen(event)
{
  ws = this.ws;

  ws.send("my-message");
}

ws = new WebSocket(...);
wsopen.ws = ws;
ws.onopen = wsopen;

</script>

Application Protocols

A typical application will implement an application-specific protocol on top of the WebSocket protocol, either a general messaging protocol like JMTP, or a simple IM protocol, or a compact binary game protocol like Quake. Most application code will use the application protocol API, and only a thin layer dealing with WebSocket itself.

The JMTP protocol below is an example of a general messaging protocol that can be layered on top of WebSockets, providing routing, request-response, and object-oriented service design.

JMTP (JSON Message Transport Protocol)

An example of a general protocol is JMTP (JSON Message Transport Protocol), which defines unidirectional and RPC messages routed to destination services, something like a simpler XMPP or SOA.

The JMTP protocol has 5 messages:

  • "message" - unidirectional message
  • "message_error" - optional error response for a message
  • "query" - request portion of a bidirectional query
  • "result" - response for a bidirectional query
  • "query_error" - error for a query

Each JMTP message has the following components:

  • "to" and "from" address, which looks like a mail address "hello-service@example.com"
  • a type "com.foo.HelloMessage"
  • a JSON payload "{'value', 15}"

The "to" and "from" allow a service or actor-oriented architecture, where the server routes messages to simple encapsulated services.

The type is used for object-oriented messaging and extensibility. A simple actor/service can implement a subset of messages and a full actor/service can implement more messages. The object-oriented messaging lets a system grow and upgrade as the application requirement evolve.

Each JMTP message is a single WebSocket text message where each component of the message is a separate line, allowing for easy parsing an debugging.

The "message" is a unidirectional message. The receiving end can process it or even ignore it. Although the receiver can return an error message, there is no requirement to do so.

Example: JMTP unidirectional message (WebSocket text)
message
to@example.com
from@browser
com.example.HelloMessage
{"value", 15}

The "query" is a request-response request with a numeric query identifier, to allow requests to be matched up with responses. The receiver must return a "response" or a "queryError" with a matching query-id, because the sender will the waiting. Since there's no requirement of ordering, several queries can be processing at the same time.

Example: JMTP query request with qid=15
query
service@example.com
client@browser
com.example.HelloQuery
15
{"search", "greeting"}

The "result" is a response to a query request with the matching numeric query identifier. Since there's no requirement of ordering, several queries can be processing at the same time.

Example: JMTP query result with qid=15
result
client@browser
service@example.com
com.example.HelloResult
15
{"greeting", "hello"}

The "query_error" is an error response to a query request with the matching numeric query identifier. The receiver must always return either a "response" or a "query_error", even if it does not understand the query, because the sender will be waiting for a response.

The "query_error" returns the original "query" request, plus a JSON map with the error information.

Example: JMTP query error with qid=15
query_error
client@browser
service@example.com
com.example.HelloQuery
15
{"greeting", "hello"}
{"group":"internal-server-error","text":"my-error","type":"cancel"}

WebSocket Protocol Overview

Handshake

WebSocket handshake
GET /test HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Extensions: sample-extension
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: my-protocol
Sec-WebSocket-Version: 6
Host: localhost
Content-Length: 0

...

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Server: Resin/1.1
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: my-protocol
Content-Length: 0
Date: Fri, 08 May 1998 09:51:31 GMT

...

WebSocket frames

After the WebSocket connection is established, all messages are encoded in lightweight packets. While the spec defines a text message and a binary message format, browsers use the text packet exclusively. (Resin's HMTP uses the binary packet format.)

Each packet has a small frame header, giving the type and the length, and allowing for fragmentation for large messages.

WebSocket text packet
x84 x0c hello, world
WebSocket binary packet
x85 x06 hello!

Demo


Copyright © 1998-2011 Caucho Technology, Inc. All rights reserved.
Resin ® is a registered trademark, and Quercustm, Ambertm, and Hessiantm are trademarks of Caucho Technology.