JSR 356, Java API for WebSocket

Johan Vos shows us how to integrate WebSockets into your applications, with one of Java EE 7’s newest JSRs

Johan Vos shows us how to integrate WebSockets into your
applications, with one of Java EE 7’s newest
JSRs. Reprinted with permission from the Oracle
Technology Network, Oracle Corporation.

For many Web-based client-server applications, the old HTTP
request-response model has its limitations. Information has to be
transmitted from the server to the client in between requests,
rather than upon request only.

A number of “hacks” have been used in the past to circumvent
this problem, for example, long polling and Comet. However, the
need for a standards-based, bidirectional and full-duplex channel
between clients and a server has only increased.

In 2011, the IETF standardized the WebSocket protocol as RFC
6455. Since then, the majority of the Web browsers are implementing
client APIs that support the WebSocket protocol. Also, a number of
Java libraries have been developed that implement the WebSocket
protocol.

The WebSocket protocol leverages the HTTP upgrade technology to
upgrade an HTTP connection to a WebSocket. Once it is upgraded, the
connection is capable of sending messages (data frames) in both
directions, independent of each other (full duplex). No headers or
cookies are required, which considerably lowers the required
bandwidth. Typically, WebSockets are used to periodically send
small messages (for example, a few bytes). Additional headers would
often make the overhead larger than the payload.

JSR 356

JSR 356, Java API for WebSocket, specifies the API that Java
developers can use when they want to integrate WebSockets into
their applications—both on the server side as well as on the Java
client side. Every implementation of the WebSocket protocol that
claims to be compliant with JSR 356 must implement this API. As a
consequence, developers can write their WebSocket-based
applications independent of the underlying WebSocket
implementation. This is a huge benefit, because it prevents a
vendor-lock and allows for more choices and freedom of libraries
and application servers.

JSR 356 is a part of the upcoming Java EE 7 standard; hence, all
Java EE 7–compliant application servers will have an implementation
of the WebSocket protocol that adheres to the JSR 356 standard.
Once they are established, WebSocket client and server peers are
symmetrical. The difference between a client API and a server API
is, therefore, minimal. JSR 356 defines a Java client API as well,
which is a subset of the full API required in Java EE 7.

A client-server application leveraging WebSockets typically
contains a server component and one or more client components, as
shown in Figure 1:

Figure 1 – A client-server
application leveraging WebSockets

In this example, the server application is written in Java, and
the WebSocket protocol details are handled by the JSR 356
implementation contained in the Java EE 7 container.

A JavaFX client can rely on any JSR 356–compliant client
implementation for handling the WebSocket-specific protocol issues.
Other clients (for example, an iOS client and an HTML5 client) can
use other (non-Java) implementations that are compliant with RFC
6455 in order to communicate with the server application.

Programming Model

The Expert Group that defined JSR 356 wanted to support patterns
and techniques that are common to Java EE developers. As a
consequence, JSR 356 leverages annotations and injection.

In general, two different programming models are supported:

Annotation-driven. Using annotated POJOs, developers can
interact with the WebSocket lifecycle events.

Interface-driven. Developers can implement
the Endpoint interface
and the methods that interact with the lifecycle events.

Lifecycle Events

The typical lifecycle event of a WebSocket interaction goes as
follows:

One peer (a client) initiates the connection by sending an HTTP
handshake request.

The other peer (the server) replies with a handshake
response.

The connection is established. From now on, the connection is
completely symmetrical.

Both peers send and receive messages.

One of the peers closes the connection.

Most of the WebSocket lifecycle events can be mapped to Java
methods, both in the annotation-driven and interface-driven
approaches.

Annotation-Driven Approach

An endpoint that is accepting incoming WebSocket requests can be
a POJO annotated with the @ServerEndpoint annotation.
This annotation tells the container that the given class should be
considered to be a WebSocket endpoint. The
required value element
specifies the path of the WebSocket endpoint.

Consider the following code snippet:

@ServerEndpoint("/hello") public class MyEndpoint { }

This code will publish an endpoint at the relative
path hello.
The path can include path parameters that are used in subsequent
method calls; for example, /hello/{userid} is
a valid path, where the value of {userid} can
be obtained in lifecycle method calls using the@PathParam annotation.

In GlassFish, if your application is deployed with the
contextroot mycontextroot in
a Web container listening at port 8080 oflocalhost,
the WebSocket will be accessible using ws://localhost:8080/mycontextroot/hello

An endpoint that should initiate a WebSocket connection can be a
POJO annotated with the @ClientEndpoint annotation.
The main difference between @ClientEndpoint and
a ServerEndpoint is
that the ClientEndpoint does
not accept a path value element, because it is not listening to
incoming requests.

@ClientEndpoint public class MyClientEndpoint {}

Initiating a WebSocket connection in Java leveraging the
annotation-driven POJO approach can be done as follows:

A Session instance
is valid as long as the WebSocket is not closed.
The Session class
contains a number of interesting methods that allow developers to
obtain more information about the connection. Also,
the Session contains
a hook to application-specific data, by means of
the getUserProperties() method
returning a Map<String,
Object>. This allows developers to
populate Sessioninstances
with session- and application-specific information that should be
shared among method invocations.

When the WebSocket endpoint receives a message, the method
annotated with @OnMessage will
be called. A method annotated with@OnMessage can
contain the following parameters:

The javax.websocket.Session parameter.

@PathParam,
referring to path parameters on the endpoint path.

The message itself. See below for an overview of possible
message types.

When a text message has been sent by the other peer, the content
of the message will be printed by the following code snippet:

If the return type of the method annotated
with @OnMessage is
not void,
the WebSocket implementation will send the return value to the
other peer. The following code snippet returns the received text
message in capitals back to the sender:

In this approach, we start from the Session object,
which can be obtained from the lifecycle callback methods (for
example, the method annotated with @OnOpen).
The getBasicRemote() method
on the Session instance
returns a representation of the other part of the WebSocket,
the RemoteEndpoint.
That RemoteEndpoint instance
can be used for sending text or other types of messages, as
described below.

When the WebSocket connection is closing, the method annotated
with @OnClose is
called. This method can take the following parameters:

The javax.websocket.Session parameter.
Note that this parameter cannot be used once the WebSocket is
really closed, which happens after the @OnClose annotated
method returns.

A javax.websocket.CloseReason parameter
describing the reason for closing the WebSocket, for example,
normal closure, protocol error, overloaded service, and so on.

Zero or more string parameters annotated
with @PathParam,
referring to path parameters on the endpoint path.

Thee following code snippet will print the reason why a
WebSocket is closing:

To be complete, there is one more lifecycle annotation: in case an
error is received, the method annotated
with @OnError will
be called.

Interface-driven approach

The annotation-driven approach allows us to annotate a Java
class and methods with lifecycle annotations. Using the
interface-driven approach, a developer
extends javax.websocket.Endpoint and
overrides the onOpen, onClose,
and onError methods:

MessageHandler is
an interface with two subinterfaces: MessageHandler.Partial and MessageHandler.Whole.
TheMessageHandler.Partial interface
should be used when the developer wants to be notified about
partial deliveries of messages, and an implementation
of MessageHandler.Whole should
be used for notification about the arrival of a complete
message.

The following code snippet listens to incoming text messages and
sends the uppercase version of the text message back to the other
peer:

Message Types, Encoders and Decoders

The Java API for WebSocket is very powerful, because it allows
any Java object to be sent or received as a WebSocket message.

Basically, there are three different types of messages:

Text-based messages

Binary messages

Pong messages, which are about the WebSocket connection
itself

When using the interface-driven model, each session can register
at most one MessageHandler for
each of these three different types of messages.

When using the annotation-driven model, for each different type
of message, one @onMessage annotated
method is allowed. The allowed parameters for specifying the
message content in the annotated methods are dependent on the type
of the message.

Any Java object can be encoded into a text-based or binary
message using an encoder. This text-based or binary message is
transmitted to the other peer, where it can be decoded into a Java
object again—or it can be interpreted by another WebSocket library.
Often, XML or JSON is used for the transmission of WebSocket
messages, and the encoding/decoding then comes down to marshaling a
Java object into XML or JSON and back.

An encoder is defined as an implementation of
the javax.websocket.Encoder interface,
and a decoder is an implementation of thejavax.websocket.Decoder interface.
Somehow, the endpoint instances need to know what the possible
encoders and decoders are. Using the annotation-driven approach, a
list of encoders and decoders is passed via the encoder and decoder
elements in the@ClientEndpoint and @ServerEndpoint annotations.

The code in Listing 1 shows how to register
a MessageEncoder class
that defines the conversion of an instance
of MyJavaObject to
a text message. A MessageDecoder class
is registered for the opposite conversion.

Conclusion

The Java API for WebSocket provides Java developers with a
standard API to integrate with the IETF WebSocket standard. By
doing so, Web clients or native clients leveraging any WebSocket
implementation can easily communicate with a Java back end.

The Java API is highly configurable and flexible, and it allows
Java developers to use their preferred patterns.

Author Bio: Johan Vos started working with Java in 1995. He
is a cofounder of LodgON, where he is working on Java-based
solutions for social networking software. An enthusiast of both
embedded and enterprise development, Vos focuses on end-to-end Java
using JavaFX and Java EE. Learn more at his blog or follow him at http://twitter.com/johanvos.

Reprinted with permission from the Oracle Technology
Network, Oracle Corporation

Johan Vos started working with Java in 1995, as part of his PhD research. He joined the Blackdown team and ported Java 1.2 to Linux/SPARC. Johan has been a Java consultant ever since, and worked for a number of companies (e.g. The Reference, Acunia). He co-founded LodgON, where the main focus is on social networking software and recently became a Java Champion. He was part of the Core Platform Expert Group of OSGi that created the OSGi platform specifications. His main technology interests are currently GlassFish and JavaFX. He holds an Msc in civil engineering (mining) and a PhD in Applied Physics.