WebSockets with Embedding Jetty [step3]
In [step2] we have implemented our chat application on client side with JavasScript WebSocket component. In this article we will create chat WebSockets on server side with Jetty-WebSocket and start the Jetty Server with Embedding Jetty. We will create a Java main which start a Jetty Server and configure it to manage WebSockets Chat Application on server side.
We will use Eclipse IDE to create a Java Project org.samples.websockets.embeddingjetty (but you can do with another IDE if you wish). At the end of this article our workspace will looks like this:
Download
You can download several Eclipse projects explained in this article:
- org.samples.websockets.embeddingjetty_step3_1.zip contains Eclipse Project org.samples.websockets.embeddingjetty explained in this section. This project is Java project with pom.xml and maven launch (for M2Eclipse) configure it to download Jetty-WebSocket JARs with maven.
- org.samples.websockets.embeddingjetty_step3_2.zip contains Eclipse Project org.samples.websockets.embeddingjetty explained in this section. This project is Java project with Jetty-WebSocket JARs stored in the lib folder.
- org.samples.websockets.embeddingjetty_step3_2.zip contains Eclipse Project org.samples.websockets.embeddingjetty explained in this section. This project contains chat.html (client side) and chat application with Jetty-WebSocket on server side.
Download Jetty-WebSockets JARs
You can download org.samples.websockets.embeddingjetty_step3_2.zip which contains Eclipse project explained in this section.
Before starting read this section, create an Eclipse Java Project org.samples.websockets.embeddingjetty.
Here we will use Jetty-WebSockets 7.4.4.v20110707 which is the last stable Jetty release. This version is very important because WebSockets API has changed since the article Jetty WebSocket Server. I would like say too that WTP Jetty Websocket_Wizard doesn’t generate well WebSockets code for the Jetty 7.4.
At first we need to download required Jetty-WebSockets JARs :
- jetty-continuation-7.4.4.v20110707.jar
- jetty-http-7.4.4.v20110707.jar
- jetty-io-7.4.4.v20110707.jar
- jetty-server-7.4.4.v20110707.jar
- jetty-util-7.4.4.v20110707.jar
- jetty-websocket-7.4.4.v20110707.jar
- servlet-api-2.5.jar
If you wish download thoses JARs you can :
- download the project org.samples.websockets.embeddingjetty_step3_2.zip which contains required Jetty-Websockets JARs.
- download at hand the required JARs at http://mvnrepository.com/artifact/org.eclipse.jetty.
- use maven.
Jetty-WebSockets POM (maven)
If you don’t know maven or you don’t know how to use maven to download JARs and you can read French, please read Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step14] where I explain how download JARs by using POM maven.
In your POM, you must declare the Maven repository of the Jetty Eclipse Repository like this :
<repository> <id>org.eclipse.jetty</id> <name>Jetty Eclipse Repository</name> <url>http://mvnrepository.com/artifact/org.eclipse.jetty</url> </repository>
After you must declare the jetty-websocket dependency like this :
<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-websocket</artifactId> <version>7.4.4.v20110707</version> </dependency>
Here the full POM that I have used to download Jetty-WebSocket JARs :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.samples.websockets</groupId> <artifactId>org.samples.websockets.embeddingjetty</artifactId> <packaging>pom</packaging> <version>0.0.1-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.1</version> <executions> <execution> <id>copy-dependencies</id> <phase>process-resources</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>lib</outputDirectory> <overWriteReleases>true</overWriteReleases> <overWriteSnapshots>true</overWriteSnapshots> <overWriteIfNewer>true</overWriteIfNewer> <excludeTypes>libd</excludeTypes> </configuration> </execution> </executions> </plugin> </plugins> </build> <repositories> <!-- Jetty Eclipse Repository --> <repository> <id>org.eclipse.jetty</id> <name>Jetty Eclipse Repository</name> <url>http://mvnrepository.com/artifact/org.eclipse.jetty</url> </repository> </repositories> <dependencies> <!-- Jetty WebSockets --> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-websocket</artifactId> <version>7.4.4.v20110707</version> </dependency> </dependencies> </project>
You can do :
mvn process-resources
Or use the launch org.samples.websockets.embeddingjetty\launch-mvn\Process-resources Jetty-Websockets.launch from the org.samples.websockets.embeddingjetty_step3_2.zip :
After refreshing the Eclipse project, lib folder must appears with Jetty-WebSockets JARs :
Update your ClassPath with Jetty-WebSockets JARs :
Simple Server with Embedding Jetty
In this section we will create a simple Embedding Jetty Server which is started in the 8081 port. To do that create the Java class org.samples.websockets.embeddingjetty.ChatWebSocketServer like this :
package org.samples.websockets.embeddingjetty; import org.eclipse.jetty.server.Server; public class ChatWebSocketServer { public static void main(String[] args) { try { // 1) Create a Jetty server with the 8091 port. Server server = new Server(8081); // 2) Start the Jetty server. server.start(); // Jetty server is stopped when the Thread is interruped. server.join(); } catch (Throwable e) { e.printStackTrace(); } } }
We have created a simple Jetty Server with Java main started on 8081 port. You can notice that the code is very simple! To test our code, start the Java main ChatWebSocketServer. The Eclipse console must display some Jetty logs which shows you Jetty Server is started in the 8081 port :
Open a Google Chrome (or another WEB Browser) and go at http://localhost:8081 :
You will see 404 error because the Jetty server has not defined HTML resources files, Servlet, Filter… If you are interested to do that, you can find several samples with Embedding Jetty at SVN example-jetty-embedded.
Chat Application with Jetty-WebSockets
In this section we create a Jetty Handler to manage WebSocket with ChatWebSocketHandler and register it to the Jetty Server instance.
ChatWebSocketHandler
In this section we create a Jetty Handler ChatWebSocketHandler to manage WebSocket.
Implements WebSocketHandler
Create org.samples.websockets.embeddingjetty.ChatWebSocketHandler like this :
package org.samples.websockets.embeddingjetty; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.websocket.WebSocketHandler; public class ChatWebSocketHandler extends WebSocketHandler { public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { return null; } }
When this handler will be registered in the Jetty Server instance and user of chat application on client side will click on « Join » button, which call the JavaScript code :
var location = "ws://localhost:8081/" this._ws = new WebSocket(location);
The Jetty server will call :
ChatWebSocketHandler#doWebSocketConnect(HttpServletRequest request, String protocol)
This method creates an instance of org.eclipse.jetty.websocket.WebSocket. The server side will store an instance of org.eclipse.jetty.websocket.WebSocket per JavasScript WebSocket component (client side).
Implements WebSocket.OnTextMessage
At this step we must implement org.eclipse.jetty.websocket.WebSocket to return an instance of this class when
ChatWebSocketHandler#doWebSocketConnect(HttpServletRequest request, String protocol)
is called.
WebSocket interface before 7.4 version
Before Jetty 7.4 version that we are using, Jetty WebSocket interface was defined like this:
package org.eclipse.jetty.websocket; import java.io.IOException; public interface WebSocket { @Deprecated public final byte LENGTH_FRAME=(byte)0x80; @Deprecated public final byte SENTINEL_FRAME=(byte)0x00; public final static byte OP_CONTINUATION = 0x00; public final static byte OP_CLOSE = 0x01; public final static byte OP_PING = 0x02; public final static byte OP_PONG = 0x03; public final static byte OP_TEXT = 0x04; public final static byte OP_BINARY = 0x05; public final static int CLOSE_NORMAL=1000; public final static int CLOSE_SHUTDOWN=1001; public final static int CLOSE_PROTOCOL=1002; public final static int CLOSE_DATA=1003; public final static int CLOSE_LARGE=1004; void onConnect(Outbound outbound); void onMessage(byte opcode,String data); void onFragment(boolean more,byte opcode,byte[] data, int offset, int length); void onMessage(byte opcode,byte[] data, int offset, int length); void onDisconnect(); // TODO add code public interface Outbound { void sendMessage(String data) throws IOException; void sendMessage(byte opcode,String data) throws IOException; void sendMessage(byte opcode,byte[] data, int offset, int length) throws IOException; void sendFragment(boolean more,byte opcode,byte[] data, int offset, int length) throws IOException; void disconnect(); void disconnect(int code,String message); boolean isOpen(); } }
When you wanted to implement Jetty-WebSocket you needed to implement several methods like WebSocket#sendMessage(byte opcode,byte[] data, int offset, int length) throws IOException which is the method to send binary data, although this method was never called. For instance in our chat application, only WebSocket#sendMessage(String data) throws IOException is used because chat application client send simple text.
WebSocket interface 7.4 version
Jetty 7.4 version has changed the WebSocket interface like this :
package org.eclipse.jetty.websocket; import java.io.IOException; /** * WebSocket Interface. * <p> * This interface provides the signature for a server-side end point of a websocket connection. * The Interface has several nested interfaces, for each type of message that may be received. */ public interface WebSocket { /** * Called when a new websocket connection is accepted. * @param connection The Connection object to use to send messages. */ void onOpen(Connection connection); /** * Called when an established websocket connection closes * @param closeCode * @param message */ void onClose(int closeCode, String message); /** * A nested WebSocket interface for receiving text messages */ interface OnTextMessage extends WebSocket { /** * Called with a complete text message when all fragments have been received. * The maximum size of text message that may be aggregated from multiple frames is set with {@link Connection#setMaxTextMessageSize(int)}. * @param data The message */ void onMessage(String data); } /** * A nested WebSocket interface for receiving binary messages */ interface OnBinaryMessage extends WebSocket { /** * Called with a complete binary message when all fragments have been received. * The maximum size of binary message that may be aggregated from multiple frames is set with {@link Connection#setMaxBinaryMessageSize(int)}. * @param data * @param offset * @param length */ void onMessage(byte[] data, int offset, int length); } /** * A nested WebSocket interface for receiving control messages */ interface OnControl extends WebSocket { /** * Called when a control message has been received. * @param controlCode * @param data * @param offset * @param length * @return true if this call has completely handled the control message and no further processing is needed. */ boolean onControl(byte controlCode,byte[] data, int offset, int length); } /** * A nested WebSocket interface for receiving any websocket frame */ interface OnFrame extends WebSocket { /** * Called when any websocket frame is received. * @param flags * @param opcode * @param data * @param offset * @param length * @return true if this call has completely handled the frame and no further processing is needed (including aggregation and/or message delivery) */ boolean onFrame(byte flags,byte opcode,byte[] data, int offset, int length); void onHandshake(FrameConnection connection); } /** * A Connection interface is passed to a WebSocket instance via the {@link WebSocket#onOpen(Connection)} to * give the application access to the specifics of the current connection. This includes methods * for sending frames and messages as well as methods for interpreting the flags and opcodes of the connection. */ public interface Connection { String getProtocol(); void sendMessage(String data) throws IOException; void sendMessage(byte[] data, int offset, int length) throws IOException; void disconnect(); boolean isOpen(); /** * @param size size<0 No aggregation of frames to messages, >=0 max size of text frame aggregation buffer in characters */ void setMaxTextMessageSize(int size); /** * @param size size<0 no aggregation of binary frames, >=0 size of binary frame aggregation buffer */ void setMaxBinaryMessageSize(int size); /** * Size in characters of the maximum text message to be received * @return size <0 No aggregation of frames to messages, >=0 max size of text frame aggregation buffer in characters */ int getMaxTextMessageSize(); /** * Size in bytes of the maximum binary message to be received * @return size <0 no aggregation of binary frames, >=0 size of binary frame aggregation buffer */ int getMaxBinaryMessageSize(); } /** * Frame Level Connection * <p>The Connection interface at the level of sending/receiving frames rather than messages. * */ public interface FrameConnection extends Connection { boolean isMessageComplete(byte flags); void close(int closeCode,String message); byte binaryOpcode(); byte textOpcode(); byte continuationOpcode(); byte finMask(); boolean isControl(byte opcode); boolean isText(byte opcode); boolean isBinary(byte opcode); boolean isContinuation(byte opcode); boolean isClose(byte opcode); boolean isPing(byte opcode); boolean isPong(byte opcode); void sendControl(byte control,byte[] data, int offset, int length) throws IOException; void sendFrame(byte flags,byte opcode,byte[] data, int offset, int length) throws IOException; } }
So now when you wish implement Jetty WebSocket you must choose the internal WebSocket interface that you wish implement. In our case text message is used for our chat application, so we will implement WebSocket.OnTextMessage :
/** * A nested WebSocket interface for receiving text messages */ interface OnTextMessage extends WebSocket { /** * Called with a complete text message when all fragments have been received. * The maximum size of text message that may be aggregated from multiple frames is set with {@link Connection#setMaxTextMessageSize(int)}. * @param data The message */ void onMessage(String data); }
Here hyerarchy of the Jetty 7.4 WebSocket interface :
Modify org.samples.websockets.embeddingjetty.ChatWebSocketHandler like this :
package org.samples.websockets.embeddingjetty; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.websocket.WebSocket; import org.eclipse.jetty.websocket.WebSocketHandler; public class ChatWebSocketHandler extends WebSocketHandler { public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { return new ChatWebSocket(); } private class ChatWebSocket implements WebSocket.OnTextMessage { public void onOpen(Connection connection) { } public void onMessage(String data) { } public void onClose(int closeCode, String message) { } } }
Implements ChatWebSocket
At this step we must implement each methods of ChatWebSocket.To do that modify org.samples.websockets.embeddingjetty.ChatWebSocketHandler like this :
package org.samples.websockets.embeddingjetty; import java.io.IOException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.websocket.WebSocket; import org.eclipse.jetty.websocket.WebSocketHandler; public class ChatWebSocketHandler extends WebSocketHandler { private final Set<ChatWebSocket> webSockets = new CopyOnWriteArraySet<ChatWebSocket>(); public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { return new ChatWebSocket(); } private class ChatWebSocket implements WebSocket.OnTextMessage { private Connection connection; public void onOpen(Connection connection) { // Client (Browser) WebSockets has opened a connection. // 1) Store the opened connection this.connection = connection; // 2) Add ChatWebSocket in the global list of ChatWebSocket // instances // instance. webSockets.add(this); } public void onMessage(String data) { // Loop for each instance of ChatWebSocket to send message server to // each client WebSockets. try { for (ChatWebSocket webSocket : webSockets) { // send a message to the current client WebSocket. webSocket.connection.sendMessage(data); } } catch (IOException x) { // Error was detected, close the ChatWebSocket client side this.connection.disconnect(); } } public void onClose(int closeCode, String message) { // Remove ChatWebSocket in the global list of ChatWebSocket // instance. webSockets.remove(this); } } }
Register ChatWebSocketHandler in the Jetty Server
At this step ChatWebSocketHandler is created and we must register it to the Jetty server instance like this:
ChatWebSocketHandler chatWebSocketHandler = new ChatWebSocketHandler(); chatWebSocketHandler.setHandler(new DefaultHandler()); server.setHandler(chatWebSocketHandler);
Modify org.samples.websockets.embeddingjetty.ChatWebSocketServer like this :
package org.samples.websockets.embeddingjetty; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.DefaultHandler; public class ChatWebSocketServer { public static void main(String[] args) { try { // 1) Create a Jetty server with the 8091 port. Server server = new Server(8081); // 2) Register ChatWebSocketHandler in the Jetty server instance. ChatWebSocketHandler chatWebSocketHandler = new ChatWebSocketHandler(); chatWebSocketHandler.setHandler(new DefaultHandler()); server.setHandler(chatWebSocketHandler); // 2) Start the Jetty server. server.start(); // Jetty server is stopped when the Thread is interruped. server.join(); } catch (Throwable e) { e.printStackTrace(); } } }
Play with Chat Application!
At this step you can play with the chat application. To do that start the Jetty Server and open chat.html in Google Chrome:
Conclusion
In this article we have seen how to develop Chat Application WebSockets on server side with Jetty-WebSocket. We are used an Embedding Jetty to start the Jetty server and configure it to support our Chat Application WebSockets with the ChatWebSocketHandler class. This class can be used in a Jetty server too. In the [step4] I see you how to use our Chat Jetty-WebSocket in another Java Server (Tomcat, WebShpere…) which doesn’t support WebSocket.
thank you so much, this is an excellent tutorial, you rock
Hi James,
I’m happy that this article pleased you. Thank a lot for your post.
Regards Angelo
Hello,
Thank you very much. The best tutorial is this.
Best Regards,
Hi Mehmet.
Many thanks for your comment.
Regards Angelo
Very good tutorial it’s help me a lot but i’m confused, when you say « You will see 404 error because the Jetty server has not defined HTML resources files »… i went to the link but i don’t understand what to do, i always have 404 error, and i don’t understand why?
can you please help me, you can respond me in french if you want
thanks you a lot
Hi Tito,
Many thank’s. Sorry I cannot help you more. It was a long time that I have written those article. Good luck.
Regards Angelo
best tutorial ever read on the subject
Wow, many thanks:)
Best tutorial, thx,
But I have an error on firefox an Chrome:
WARN::Unsupported Websocket version: 13
Someone have an issue about it ? thx
upgrade to the last version of jetty 8 and servlet 3.0 API
Thanks Phil for the same it worked. Very helpful guide a must read for someone trying to learn how websockets works with embedded jetty
Hello,
Really good tutorial,
Thank u a lot,
I did it with jetty-websocket et jetty-server 8.1.0.RC5 + chrome 24 (cause 7.4.4.v20110707 was out of date)
Hi Angelo,
Do you know if there is a way to set a mime type for the messages being set from jetty? This is so we can query on the client if the message received was text or binary. In my case, Chrome issues a warning « Resource interpreted as Image but transferred with MIME type text/plain: … » when I send images.
Regards,
Marat
Very good tuto, very helpfull. Thank you.
Can you please explain how to deal with secure websockets (wss) with jetty 8
I have written server as follows:
import java.net.*;
import org.eclipse.jetty.http.*;
import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.util.*;
import org.eclipse.jetty.websocket.*;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.server.*;
public class Server {
public static void main(String[] args) {
try{
String jetty_home = System.getProperty(« jetty.home », »/opt/jetty »);
System.setProperty(« jetty.home »,jetty_home);
// === Create a Jetty Server ===
Server server = new Server();
// === jetty-https.xml ===
// SSL Context Factory
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(jetty_home + « /etc/keystore.jks »);
sslContextFactory.setKeyStorePassword(« jettydev »);
//sslContextFactory.setKeyManagerPassword(« jettydev »);
sslContextFactory.setTrustStore(jetty_home + « /etc/keystore.jks »);
sslContextFactory.setTrustStorePassword(« jettydev »);
sslContextFactory.setIncludeProtocols(new String[] { « TLSv1″, »SSLv2Hello » });
// === SSL Connector (for https) ===
SslSelectChannelConnector sslConnector = new SslSelectChannelConnector(sslContextFactory);
sslConnector.setPort(443);
// === Connector (for http) ===
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(8086);
server.setConnectors(new Connector[] {sslConnector, connector} );
// === Register SocketHandler in the Jetty server instance. ===
SocketHandler socketHandler = new SocketHandler();
socketHandler.setHandler(new DefaultHandler());
server.setHandler(socketHandler);
// === Start the Jetty Server ===
server.start();
// === Jetty Server is stopped when the thread is interrupted. ===
server.join();
} catch(Exception e) {
System.out.println(« Exception occured: « );
e.printStackTrace();
}
}
}
So, here 2 connectors are used. One for http requests and other for https requests.
At client side,
for nonsecure websocket,
ws = new Websocket(« ws://domain:8086/ »);
works absolutely fine.
Whereas,
for secure websocket,
ws = new Websocket(« wss://domain:443/ »);
fails to form websocket connection.
It does not reach server it seems.
Can you please tell me why this happens and what else I need to do or what am I missing??
I really hope you reply as early as possible. Its an urgent issue actually.
Please help!
Never done security with WebSocket. I cannot help you.