Resin 4 Java EE Basic Servlet, Comet, and WebSocket Tutorial
From Resin 4.0 Wiki
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:
- Process the initial request (
service
). - Register the
AsyncContext
with the service (service
). - 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:
servlet.service()
is called for the initial request_service.addCometState()
registers with theTimerService
- after the
service()
completes, Resin suspends the servlet. - The
TimerService
detects an event, in this case the timer event. - The
TimerService
callsrequest.setAttribute()
to send new data. - The
TimerService
callsasync.dispatch()
to wake the servlet. servlet.resume()
processes the data and sends the next packet.- After the
resume()
completes, Resin suspends the servlet again and we repeat as after step #3. - After the 10th data, the
TimerService
callscontroller.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:
servlet = new test.HelloServlet();
servlet.setGreeting("Hello, World!");
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:
- Connects to the Resin WebSocket servlet
- Sends a "hello" query to the servlet
- Sends a "server" query to the servlet
- Displays any received messages from the servlet
Correspondingly, the server does the following:
- Checks the handshake for a valid "hello" protocol.
- Dispatches a HelloListener to handle the web socket request.
- 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:
- Connects to the Resin WebSocket servlet
- Sends a "hello" query to the servlet
- Sends a "server" query to the servlet
- 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"); }