Caucho Technology
  • resin 4.0
  • 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 a startWebSocket call.

    (The WebSocketServletRequest API is temporary until the next Servlet specification integrates the startWebSocket method 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

    The WebSocketContext is used to send messages. Applications will need to synchronize on the WebSocketContext when sending messages, because WebSockets is designed for multithreaded applications, and because the WebSocketContext is not thread safe.

    A message stream starts with startTextMessage or startBinaryMessage and is then used like a normal PrintWriter or OutputStream. Closing the stream finishes the message. A new message cannot be started until the first message is completed by calling close().

    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 the onRead method, expecting the listener to read data from the client. While the onRead is processing, Resin will not call onRead again until the first one has completed processing.

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

    The Reader and PrintWriter from the WebSocketContext are 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.

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

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

    onDisconnect is called when the socket is shut down. So a graceful close will have onClose followed by onDisconnect, while a dropped connection will only have an onDisconnect.

    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.