Resin 4 Java EE Basic Servlet, Comet, and WebSocket Tutorial

From Resin 4.0 Wiki

Jump to: navigation, search

Contents

Servlet Tutorials

A Hello, World Servlet

A trivial "hello, world" servlet

Files in this tutorial

File Description
WEB-INF/resin-web.xml resin-web.xml configuration
WEB-INF/classes/test/HelloServlet.java The Hello, World servlet.

Servlets are the pure Java solution to handle web requests. Many application will use servlets instead of JSP and others will use servlets in conjunction with JSP. Experienced JSP programmers use servlets in conjunction with JSP to create clearer and simpler applications. The servlets handle Java processing: form handing, calculation and database queries. JSP formats the results.

Servlets belong in WEB-INF/classes. On this machine, the source is in Java source in WEB-INF/classes. WEB-INF/classes is the standard location for servlets and other Java classes. Resin automatically reloads and recompiles servlets, beans, and classes placed in WEB-INF/classes. You should make some changes and add errors to become familiar with Resin's recompilation and the error reporting.

Create the following servlet in WEB-INF/classes/test/HelloServlet.java with your favorite text editor: notepad, emacs, vi, or whatever.

Example: WEB-INF/classes/test/HelloServlet.java

package test;

import java.io.*;

import javax.servlet.http.*;
import javax.servlet.*;

public class HelloServlet extends HttpServlet {
  public void doGet (HttpServletRequest req,
                     HttpServletResponse res)
    throws ServletException, IOException
  {
    PrintWriter out = res.getWriter();

    out.println("Hello, world!");
    out.close();
  }
}

Now browse the servlet at [hello http://localhost:8080/resin-doc/tutorial/hello]. Resin will automatically compiles the servlet for you. Browsing servlets differs from page browsing because you're executing a servlet class, not looking at a page. The /hello URL is configured for the hello, world servlet below.

Configuration

Configuration for the servlet is in the WEB-INF/web.xml file.

The servlet needs to be configured and it needs to be mapped to a URL. The <a config-tag="servlet"/> tag configures the servlet. In our simple example, we just need to specify the class name for the servlet.

The <a config-tag="servlet-mapping"/> tag specifies the URLs which will invoke the servlet. In our case, the /hello URL invokes the servlet. Because the tutorial [doc|webapp.xtp webapp] is a sub-URL like /doc/servlet/tutorial/helloworld, the actual URL to invoke the servlet is the combination of the two.

Example: WEB-INF/web.xml

<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http:/java.sun.com/dtd/web-app_2_3.dtd">
  <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>test.HelloServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
</web-app>

Resin allows a short cut for the XML configuration in the example above; you can use XML attributes in place of elements. The Servlet 2.4 standard uses only elements. So the servlet-mapping configuration following the Servlet 2.4 standard would look like:

Example: WEB-INF/resin-web.xml

<web-app xmlns="http://caucho.com/ns/resin">

   <servlet-mapping url-pattern="/hello"
            servlet-class="test.HelloServlet"/>

</web-app>

The two are entirely equivalent. For larger configurations, using attributes makes the resin.conf or web.xml more readable.

tag meaning
web-app Web application top-level tag.

The xmlns="http://caucho.com/ns/resin" lets Resin validate the web.xml configuration. The validator will catch most errors in the web.xml.


Server-Push Servlet

Resin's server-push (Comet) servlet API enables streaming communication such as reverse AJAX dynamic updates for browser/JavaScript applications. The API encapsulates of the threading and communications issues between the request threads and the rest of the application.


Resin's server-push (Comet) API lets server application push new data to the client as it becomes available. Administration and monitoring applications need to continually update the client when new information becomes available to the server.

<figure src="cometcomm.png" alt="javascript/api <-> <- java/api" />

The architecture in the picture uses two HTTP streams to the server application, one for normal client-server requests, and a second unidirectional stream to send updates from the server to the client. In this example, we're using a browser with JavaScript, but the same architecture applies to a more sophisticated Flash monitoring application sending Hessian packets to update the monitoring display.


Files in this tutorial

File Description
WEB-INF/resin-web.xml resin-web.xml configuration
WEB-INF/beans.xml Java Injection beans.xml marker for class scanning
WEB-INF/classes/example/TestCometServlet.java The Comet servlet.
WEB-INF/classes/example/TimerService.java Application service for timer/comet events.
WEB-INF/classes/example/CometState.java The application's Comet controller state.
comet.html Main HTML page linking comet servlet.


Example Overview

The example updates a comet.html page every two seconds with new data. In this case, just an updated counter.

The components of the Comet/AJAX application look like:

  • Protocol: JavaScript function calls with a trivial argument.
  • Client:
    • View: HTML updated by JavaScript AJAX
    • Controller: call server with an <iframe>
  • Server:
    • Service: TimerService manages the comet connections and wakes them with new data.
    • Servlet: TestCometServlet generates <script> protocol tags from new data from the TimerService on each resume.
    • State: CometState encapsulates both the item's state (the timer count), and the CometController needed to wake the servlet and pass updated data.


Streaming Protocol: <script> tags

The comet HTTP stream is a sequence of <script> tags containing JavaScript commands to update the browser's display. Because the browser executes the script as part of its progressive rendering, the user will see the updates immediately without waiting for the entire HTTP request to complete.

In our example, the packet is a JavaScript comet_update(data) call, which updates the text field with new data. Here's an example of the packet stream:

Update JavaScript packets

<script type="text/javascript">
window.parent.comet_update(1);
</script>

<!-- 2 second delay -->

<script type="text/javascript">
window.parent.comet_update(2);
</script>

<!-- 2 second delay -->

<script type="text/javascript">
window.parent.comet_update(3);
</script>

More sophisticated comet applications will use a dynamic-typed protocol to update the client. Browser-based applications could use JSON to update the client and Flash-based applications might use Hessian. In all cases, the protocol must be kept simple and designed for the client's requirements. Design separate, simple protocols for Flash and JavaScript browsers, rather than trying to create some complicated general protocol.


Browser Client

The JavaScript command stream updates a parent HTML file which defines the JavaScript commands and launches the Comet servlet request with an <iframe> tag. Our comet_update function finds the HTML tag with id="content" and updates its HTML content with the new data from the server.

comet.html

<html>
<body>

Server Data:
<span id="content">server data will be shown here</span>

<script type="text/javascript">
function comet_update(value) {
  document.getElementById('content').innerHTML = value;
};
</script>

<iframe src="comet"
        style="width:1px;height:1px;position:absolute;top:-1000px"></iframe>

</body>
</html>


AsyncContext

The AsyncContext is the Servlet 3.0 encapsulation of control and communication from the application's service to the Comet servlet. Applications may safely pass the AsyncContext to different threads, wake the servlet with dispatch(), and send data with the request returned by getAttribute.

In the example, the TimerService passes the updated count to the servlet by calling setAttribute("caucho.count", count), and wakes the servlet by calling dispatch(). When the servlet resumes, it will retrieve the count using request.getAttribute("caucho.count"). Note, applications must only use the thread-safe AsyncContext in other threads. As with other servlets, the ServletRequest, ServletResponse, writers and output stream can only be used by the servlet thread itself, never by any other threads.

javax.servlet.AsyncContext

package javax.servlet;

public interface AsyncContext;
{
  public ServletRequest getRequest();
  public ServletResponse getResponse();

  public boolean hasOriginalRequestAndResponse();

  public void complete();
  
  public void dispatch();
  public void dispatch(String path);
  public void dispatch(ServletContext context, String path);

  public void start(Runnable run);
}


Comet Servlet

The comet servlet has three major responsibilities:

  1. Process the initial request (service).
  2. Register the AsyncContext with the service (service).
  3. Send streaming data as it becomes available (resume).

Like other servlets, only the comet servlet may use the ServletRequest, ServletResponse or any output writer or stream. No other thread may use these servlet objects, and the application must never store these objects in fields or objects accessible by other threads. Even in a comet servlet, the servlet objects are not thread-safe. Other services and threads must use the AsyncContext to communicate with the servlet.

Process the initial request: our servlet just calls setContentType("text/html"), since it's a trivial example. A real application would do necessary database lookups and possibly send more complicated data to the client.

Register the AsyncContext: our servlet registers the controller with the timer service by calling addCometState. In general, the application state object will contain the CometController as part of the registration process.

Send streaming data:. The TimerService will set new data in the "comet.count" attribute and dispatch() the controller. When the servlet executes the resume() method, it will retrieve the data, and send the next packet to the client.

example/TestCometServlet.java

package example;

import java.io.*;

import javax.servlet.http.*;
import javax.servlet.*;
import javax.inject.Current;

public class TestComet extends GenericCometServlet {
  @Current private TimerService _timerService;

  @Override
  public void service(ServletRequest request,
                      ServletResponse response)
    throws IOException, ServletException
  {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    AsyncContext async = request.getAsyncContext();

    if (async != null) {
      resume(request, response, async);
      return;
    }

    res.setContentType("text/html");

    async = request.startAsync();

    TestState state = new TestState(async);

    _timerService.addCometState(state);
  }
  
  private void resume(ServletRequest request,
                      ServletResponse response,
                      AsyncContext async)
    throws IOException, ServletException
  {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    PrintWriter out = res.getWriter();

    String count = req.getAttribute("comet.count");

    out.print("<script type='text/javascript'>");
    out.print("comet_update(" + count + ");");
    out.print("</script>");
  }
}

The connection can close for a number of reasons. The service might call AsyncContext.complete() which will also close the connection. Finally, the client may close the connection itself.

The sequence of calls for the example looks like the following:

  1. servlet.service() is called for the initial request
  2. _service.addCometState() registers with the TimerService
  3. after the service() completes, Resin suspends the servlet.
  4. The TimerService detects an event, in this case the timer event.
  5. The TimerService calls request.setAttribute() to send new data.
  6. The TimerService calls async.dispatch() to wake the servlet.
  7. servlet.resume() processes the data and sends the next packet.
  8. After the resume() completes, Resin suspends the servlet again and we repeat as after step #3.
  9. After the 10th data, the TimerService calls controller.close(), closing the servlet connection.

Comet Servlet State Machine

The sequence of comet servlet calls looks like the following state machine. After the initial request, the servlet spends most of its time suspended, waiting for the TimerService to call complete().

<figure src="cometstate.png" alt="comet state machine"/>


Dependency-injection servlet configuration

Resin allows servlets to be configured using dependency injection.

With Resin, servlets can use Java Bean-style configuration. A "Java Bean" is just a Java class that follows a simple set of rules. Each configuration parameter foo has a corresponding setter method setFoo with a single argument for the value. Resin can look at the class using Java's reflection and find the setFoo method. Because Resin can find the bean-style setters from looking at the class, it can configure those setters in a configuration file like the web.xml.

Files in this tutorial

File Description
WEB-INF/web.xml Configures the Servlet with bean-style init
WEB-INF/classes/test/HelloServlet.java The servlet implementation.

HelloServlet

The following HelloServlet servlet is a trivial bean-style servlet. Instead of hardcoding the "Hello, world" string, it lets the web.xml configure the string as greeting. To make that work, HelloWorld adds a bean-style setGreeting(String) jmethod.

WEB-INF/classes/test/HelloServlet.java

package test;

import java.io.*;

import javax.servlet.http.*;
import javax.servlet.*;

public class HelloServlet extends HttpServlet {
  private String _greeting = "Default";

  public void setGreeting(String greeting)
  {
    _greeting = greeting;
  }

  public void doGet (HttpServletRequest req,
                     HttpServletResponse res)
    throws ServletException, IOException
  {
    PrintWriter out = res.getWriter();

    out.println(_greeting);
    out.close();
  }
}


Configuration

The <a config-tag="servlet"/> configuration sets the greeting property inside an <a config-tag="init/servlet"/> tag. After Resin instantiates the servlet object, it looks at the configuration file for any <init> section. Resin then calls a setXXX method for each <xxx> tag in <init>. In this case, Resin will call setGreeting


Resin will perform any type conversion necessary, so you can use integers and doubles as well as strings. After Resin calls the setXXX methods, it will call the init(ServletConfig) method.

When Resin initializes the servlet, it will make the following calls:

  1. servlet = new test.HelloServlet();
  2. servlet.setGreeting("Hello, World!");
  3. servlet.init(servletConfig);

WEB-INF/web.xml

<web-app xmlns="http://caucho.com/ns/resin"
            xmlns:test="urn:java:test"
            xmlns:ee="urn:java:ee">
  
  <test:HelloServlet>
    <ee:WebServlet urlPatterns="/hello"/>

    <test:greeting>Hello, bean-style World</test:greeting>
  </test:HelloServlet>
  
</web-app>


Hello, World WebSocket in Resin

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

File Description
websocket.php websocket HTML page and JavaScript
WEB-INF/classes/example/HelloWebSocketServlet.java servlet to upgrade HTTP handshake to websocket
WEB-INF/classes/example/WebSocketListener.java websocket listener for client messages
WEB-INF/resin-web.xml resin-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.

<def title="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;

} </def>


WebSocketContext

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

<def title="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();

} </def>


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.

<def title="WebSocket text packet"> x84 x0c hello, world </def>

<def title="WebSocket binary packet"> x85 x06 hello! </def>


Hello, World WebSocket in Quercus/PHP

A "hello, world" WebSocket servlet using the Quercus PHP implementation

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.

Essentially, a WebSocket is a standard bidirectional TCP socket 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.

WebSocket handshake

GET /test HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Origin: http://localhost/test
Host: localhost
Content-Length: 0

...

HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Server: Resin/4.0.2
WebSocket-Location: ws://localhost/websocket
WebSocket-Origin: http://localhost/test
Content-Length: 0
Date: Fri, 08 May 2009 09:51:31 GMT

...

WebSocket packets

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

A text packet is the byte 0x00 followed by UTF-8 encoded data followed by a 0xff byte.

<def title="WebSocket text packet"> x00 utf-8-data xff </def>

<def title="Example: hello text packet"> \x00hello, world\xff </def>



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


Files in this tutorial

File Description
client.php client HTML page and JavaScript
websocket.php PHP WebSocket launcher - accepting the upgrade request
websocket-handler.php PHP WebSocket handler - handles request messages

WebSocket JavaScript

The JavaScript for this example has been tested with the nightly build of Chromium.

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>


WebSocket PHP

Resin's WebSocket PHP support requires two PHP files. The first accepts the WebSocket request and Upgrades the HTTP request. The second handles WebSocket messages.

To upgrade a HTTP socket to WebSocket, use websocket_start(path). The path is a PHP include path to handle the request.

Example: Upgrading to WebSocket

<?php

$ws = websocket_start("websocket-handler.php");

The WebSocket handler 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 script, expecting it to read data from the client. While the handler is processing, Resin will not call it again until the first one has completed processing.

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

Example: websocket-handler.php

<?php

// read the next packet from the client 
$value = websocket_read();

if ($value == "hello") {
 websocket_write("world");
} 
else if ($value == "server") {
 websocket_write("Resin PHP");
}
Personal tools
TOOLBOX
LANGUAGES