(For more resources related to this topic, see here.)
Encoders and decoders in Java API for WebSockets
As seen in the previous chapter, the class-level annotation @ServerEndpoint indicates that a Java class is a WebSocket endpoint at runtime. The value attribute is used to specify a URI mapping for the endpoint. Additionally the user can add encoder and decoder attributes to encode application objects into WebSocket messages and WebSocket messages into application objects.
The following table summarizes the @ServerEndpoint annotation and its attributes:
Annotation |
Attribute |
Description |
@ServerEndpoint |
|
This class-level annotation signifies that the Java class is a WebSockets server endpoint. |
|
value |
The value is the URI with a leading ‘/.’ |
|
encoders |
The encoders contains a list of Java classes that act as encoders for the endpoint. The classes must implement the Encoder interface. |
|
decoders |
The decoders contains a list of Java classes that act as decoders for the endpoint. The classes must implement the Decoder interface. |
|
configurator |
The configurator attribute allows the developer to plug in their implementation of ServerEndpoint.Configurator that is used when configuring the server endpoint. |
|
subprotocols |
The sub protocols attribute contains a list of sub protocols that the endpoint can support. |
In this section we shall look at providing encoder and decoder implementations for our WebSockets endpoint.
The preceding diagram shows how encoders will take an application object and convert it to a WebSockets message. Decoders will take a WebSockets message and convert to an application object. Here is a simple example where a client sends a WebSockets message to a WebSockets java endpoint that is annotated with @ServerEndpoint and decorated with encoder and decoder class. The decoder will decode the WebSockets message and send back the same message to the client. The encoder will convert the message to a WebSockets message. This sample is also included in the code bundle for the book.
Here is the code to define ServerEndpoint with value for encoders and decoders:
@ServerEndpoint(value="/book", encoders={MyEncoder.class},
decoders = {MyDecoder.class} ) public class BookCollection { @OnMessage public void onMessage(Book book,Session session) { try { session.getBasicRemote().sendObject(book); } catch (Exception ex) { ex.printStackTrace(); } } @OnOpen public void onOpen(Session session) { System.out.println("Opening socket" +session.getBasicRemote() ); } @OnClose public void onClose(Session session) { System.out.println("Closing socket" + session.getBasicRemote()); } }
In the preceding code snippet, you can see the class BookCollection is annotated with @ServerEndpoint. The value=/book attribute provides URI mapping for the endpoint. The @ServerEndpoint also takes the encoders and decoders to be used during the WebSocket transmission. Once a WebSocket connection has been established, a session is created and the method annotated with @OnOpen will be called. When the WebSocket endpoint receives a message, the method annotated with @OnMessage will be called. In our sample the method simply sends the book object using the Session.getBasicRemote() which will get a reference to the RemoteEndpoint and send the message synchronously.
Encoders can be used to convert a custom user-defined object in a text message, TextStream, BinaryStream, or BinaryMessage format.
An implementation of an encoder class for text messages is as follows:
public class MyEncoder implements Encoder.Text<Book> { @Override public String encode(Book book) throws EncodeException { return book.getJson().toString(); } }
As shown in the preceding code, the encoder class implements Encoder.Text<Book>. There is an encode method that is overridden and which converts a book and sends it as a JSON string. (More on JSON APIs is covered in detail in the next chapter)
Decoders can be used to decode WebSockets messages in custom user-defined objects. They can decode in text, TextStream, and binary or BinaryStream format.
Here is a code for a decoder class:
public class MyDecoder implements Decoder.Text<Book> { @Override public Book decode(String string) throws DecodeException { javax.json.JsonObject jsonObject = javax.json.Json.createReader
(new StringReader(string)).readObject(); return new Book(jsonObject); } @Override public boolean willDecode(String string) { try { javax.json.Json.createReader(new StringReader
(string)).readObject(); return true; } catch (Exception ex) { } return false; }
In the preceding code snippet, the Decoder.Text needs two methods to be overridden. The willDecode() method checks if it can handle this object and decode it. The decode() method decodes the string into an object of type Book by using the JSON-P API javax.json.Json.createReader().
The following code snippet shows the user-defined class Book:
public class Book { public Book() {} JsonObject jsonObject; public Book(JsonObject json) { this.jsonObject = json; } public JsonObject getJson() { return jsonObject; } public void setJson(JsonObject json) { this.jsonObject = json; } public Book(String message) { jsonObject = Json.createReader(new
StringReader(message)).readObject(); } public String toString () { StringWriter writer = new StringWriter(); Json.createWriter(writer).write(jsonObject); return writer.toString(); } }
The Book class is a user-defined class that takes the JSON object sent by the client. Here is an example of how the JSON details are sent to the WebSockets endpoints from JavaScript.
var json = JSON.stringify({ "name": "Java 7 JAX-WS Web Services", "author":"Deepak Vohra", "isbn": "123456789" }); function addBook() { websocket.send(json); }
The client sends the message using websocket.send() which will cause the onMessage() of the BookCollection.java to be invoked. The BookCollection.java will return the same book to the client. In the process, the decoder will decode the WebSockets message when it is received. To send back the same Book object, first the encoder will encode the Book object to a WebSockets message and send it to the client.
The Java WebSocket Client API
WebSockets and Server-sent Events , covered the Java WebSockets client API. Any POJO can be transformed into a WebSockets client by annotating it with @ClientEndpoint.
Additionally the user can add encoders and decoders attributes to the @ClientEndpoint annotation to encode application objects into WebSockets messages and WebSockets messages into application objects.
The following table shows the @ClientEndpoint annotation and its attributes:
Annotation |
Attribute |
Description |
@ClientEndpoint |
|
This class-level annotation signifies that the Java class is a WebSockets client that will connect to a WebSockets server endpoint. |
|
value |
The value is the URI with a leading /. |
|
encoders |
The encoders contain a list of Java classes that act as encoders for the endpoint. The classes must implement the encoder interface. |
|
decoders |
The decoders contain a list of Java classes that act as decoders for the endpoint. The classes must implement the decoder interface. |
|
configurator |
The configurator attribute allows the developer to plug in their implementation of ClientEndpoint.Configurator, which is used when configuring the client endpoint. |
|
subprotocols |
The sub protocols attribute contains a list of sub protocols that the endpoint can support. |
Sending different kinds of message data: blob/binary
Using JavaScript we can traditionally send JSON or XML as strings. However, HTML5 allows applications to work with binary data to improve performance. WebSockets supports two kinds of binary data
- Binary Large Objects (blob)
- arraybuffer
A WebSocket can work with only one of the formats at any given time.
Using the binaryType property of a WebSocket, you can switch between using blob or arraybuffer:
websocket.binaryType = "blob"; // receive some blob data websocket.binaryType = "arraybuffer"; // now receive ArrayBuffer data
The following code snippet shows how to display images sent by a server using WebSockets.
Here is a code snippet for how to send binary data with WebSockets:
websocket.binaryType = 'arraybuffer';
The preceding code snippet sets the binaryType property of websocket to arraybuffer.
websocket.onmessage = function(msg) { var arrayBuffer = msg.data; var bytes = new Uint8Array(arrayBuffer); var image = document.getElementById('image'); image.src = 'data:image/png;base64,'+encode(bytes); }
When the onmessage is called the arrayBuffer is initialized to the message.data. The Uint8Array type represents an array of 8-bit unsigned integers. The image.src value is in line using the data URI scheme.
Security and WebSockets
WebSockets are secured using the web container security model. A WebSockets developer can declare whether the access to the WebSocket server endpoint needs to be authenticated, who can access it, or if it needs an encrypted connection.
A WebSockets endpoint which is mapped to a ws:// URI is protected under the deployment descriptor with http:// URI with the same hostname,port path since the initial handshake is from the HTTP connection. So, WebSockets developers can assign an authentication scheme, user roles, and a transport guarantee to any WebSockets endpoints.
We will take the same sample as we saw in , WebSockets and Server-sent Events , and make it a secure WebSockets application.
Here is the web.xml for a secure WebSocket endpoint:
<web-app version="3.0"
xsi_schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <security-constraint> <web-resource-collection> <web-resource-name>BookCollection</web-resource-name> <url-pattern>/index.jsp</url-pattern> <http-method>PUT</http-method> <http-method>POST</http-method> <http-method>DELETE</http-method> <http-method>GET</http-method> </web-resource-collection> <user-data-constraint> <description>SSL</description> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint> </web-app>
As you can see in the preceding snippet, we used <transport-guarantee>CONFIDENTIAL</transport-guarantee>.
The Java EE specification followed by application servers provides different levels of transport guarantee on the communication between clients and application server. The three levels are:
- Data Confidentiality (CONFIDENTIAL) : We use this level to guarantee that all communication between client and server goes through the SSL layer and connections won’t be accepted over a non-secure channel.
- Data Integrity (INTEGRAL) : We can use this level when a full encryption is not required but we want our data to be transmitted to and from a client in such a way that, if anyone changed the data, we could detect the change.
- Any type of connection (NONE) : We can use this level to force the container to accept connections on HTTP and HTTPs.
The following steps should be followed for setting up SSL and running our sample to show a secure WebSockets application deployed in Glassfish.
- Generate the server certificate:
keytool -genkey -alias server-alias -keyalg RSA
-keypass changeit --storepass changeit -keystore keystore.jks - Export the generated server certificate in keystore.jks into the file server.cer:
keytool -export -alias server-alias -storepass
changeit -file server.cer -keystore keystore.jks - Create the trust-store file cacerts.jks and add the server certificate to the trust store:
keytool -import -v -trustcacerts -alias server-alias -file server.cer
-keystore cacerts.jks -keypass changeit -storepass changeit - Change the following JVM options so that they point to the location and name of the new keystore. Add this in domain.xml under java-config:
<jvm-options>-Djavax.net.ssl.keyStore=${com.sun.aas.instanceRoot}
/config/keystore.jks</jvm-options> <jvm-options>-Djavax.net.ssl.trustStore=
${com.sun.aas.instanceRoot}/config/cacerts.jks</jvm-options> - Restart GlassFish. If you go to https://localhost:8181/helloworld-ws/, you can see the secure WebSocket application.
- Here is how the the headers look under Chrome Developer Tools:
- Open Chrome Browser and click on View and then on Developer Tools .
- Click on Network .
- Select book under element name and click on Frames .
As you can see in the preceding screenshot, since the application is secured using SSL the WebSockets URI, it also contains wss://, which means WebSockets over SSL.
So far we have seen the encoders and decoders for WebSockets messages. We also covered how to send binary data using WebSockets. Additionally we have demonstrated a sample on how to secure WebSockets based application. We shall now cover the best practices for WebSocket based-applications.