hello, world websocket in resin
A "hello, world" WebSocket servlet demonstrating the Resin WebSocket API.
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. Since the tutorial is a hello, world, the JavaScript just does the following:
Correspondingly, the server does the following:
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:
WebSocket handshake - starting the connectionTo upgrade a HTTP socket to WebSocket, the ServletRequest is cast to
a WebSocketServletRequest (implemented by Resin), and then websockets is
started with a (The 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 messagesThe A message stream starts with public void sendHello(WebSocketContext webSocket) throws IOException { PrintWriter out = webSocket.startTextMessage(); out.println("hello"); out.println("world"); out.close(); } WebSocketListener - receiving messagesThe 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 In this example, the handler reads a WebSocket text packet and sends a response. The 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(); } } WebSocketListenerResin'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
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; } WebSocketContextThe WebSocket context gives access to the WebSocket streams, as well as allowing setting of the socket timeout, and closing the connection. 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(); } Connecting to the WebSocket 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<script language='javascript'> function wsmessage(event) { data = event.data; alert("Received: [" + data + "]"); } </script> Sending WebSocket data in JavaScript<script language='javascript'> function wsopen(event) { ws = this.ws; ws.send("my-message"); } ws = new WebSocket(...); wsopen.ws = ws; ws.onopen = wsopen; </script> 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:
Each JMTP message has the following components:
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. 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. 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. 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. query_error client@browser service@example.com com.example.HelloQuery 15 {"greeting", "hello"} {"group":"internal-server-error","text":"my-error","type":"cancel"} HandshakeGET /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 framesAfter 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. x84 x0c hello, world x85 x06 hello!
|