Table of Contents
This chapter provides a quick introduction on how to get started building WebSocket services using Java API for WebSocket and Tyrus. The example described here presents how to implement simple websocket service as JavaEE web application that can be deployed on any servlet container supporting Servlet 5 and higher. It also discusses starting Tyrus in standalone mode.
First, to use the Java API for WebSocket in your project you need to depend on the following artifact:
<dependency> <groupId>jakarta.websocket</groupId> <artifactId>jakarta.websocket-api</artifactId> <version>2.2.0</version> </dependency>
In this section we will create a simple server side websocket endpoint which will echo the received message back to the sender. We will deploy this endpoint on the container.
In Java API for WebSocket and Tyrus, there are two basic approaches how to create an endpoint - either annotated endpoint,
or programmatic endpoint.
By annotated endpoint we mean endpoint constructed by using annotations (jakarta.websocket.server.ServerEndpoint
for server endpoint and jakarta.websocket.ClientEndpoint
for client endpoint), like in
"Annotated Echo Endpoint".
Example 1.1. Annotated Echo Endpoint
@ServerEndpoint(value = "/echo") public class EchoEndpointAnnotated { @OnMessage public String onMessage(String message, Session session) { return message; } }
The functionality of the EchoEndpointAnnotated
is fairly simple - to send the received message
back to the sender. To turn a POJO (Plain Old Java Object) to WebSocket server endpoint, the annotation
@ServerEndpoint(value = "/echo")
needs to be put on the POJO - see line 1. The URI path of the endpoint
is "/echo"
. The annotation @OnMessage
- line 3 on the method public String
onMessage(String message, Session session)
indicates that this method
will be called whenever text message is received. On line 5 in this method the message is sent back to
the user by returning it from the message.
The application containing only the EchoEndpointAnnotated
class can be deployed to the container.
Let's create the client part of the application. The client part may be written in JavaScript or any
other technology supporting WebSockets. We will use Java API for WebSocket and Tyrus to demonstrate how to develop
programmatic client endpoint.
The following code is used as a client part to communicate with the EchoEndpoint
deployed on server
using Tyrus and Java API for WebSocket.
The example "Client Endpoint" utilizes the concept
of the programmatic endpoint. By programmatic endpoint we mean endpoint which is created by extending
class jakarta.websocket.Endpoint
.
The example is standalone java application which needs to depend on some Tyrus artifacts to work
correctly, see "Tyrus Standalone Mode".
In the example first the CountDownLatch
is initialized. It is needed as a bocking data
structure - on line 31 it either waits for 100 seconds, or until it gets counted down (line 22).
On line 9 the jakarta.websocket.ClientEndpointConfig
is created - we will need it later
to connect the endpoint to the server. On line 11 the org.glassfish.tyrus.client.ClientManager
is created. it implements the jakarta.websocket.WebSocketContainer
and is used to connect
to server. This happens on next line. The client endpoint functionality is contained in the
jakarta.websocket.Endpoint
lazy instantiation. In the onOpen
method new MessageHandler
is registered (the received message is just printed on the console and the latch is counted down). After
the registration the message is sent to the server (line 25).
Example 1.2. Client Endpoint
public class DocClient { private static CountDownLatch messageLatch; private static final String SENT_MESSAGE = "Hello World"; public static void main(String [] args){ try { messageLatch = new CountDownLatch(1); final ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build(); ClientManager client = ClientManager.createClient(); client.connectToServer(new Endpoint() { @Override public void onOpen(Session session, EndpointConfig config) { try { session.addMessageHandler(new MessageHandler.Whole<String>() { @Override public void onMessage(String message) { System.out.println("Received message: "+message); messageLatch.countDown(); } }); session.getBasicRemote().sendText(SENT_MESSAGE); } catch (IOException e) { e.printStackTrace(); } } }, cec, new URI("ws://localhost:8025/websockets/echo")); messageLatch.await(100, TimeUnit.SECONDS); } catch (Exception e) { e.printStackTrace(); } } }
Similarly to "Client Endpoint" the server registered endpoint may also be the programmatic one:
Example 1.3. Programmatic Echo Endpoint
public class EchoEndpointProgrammatic extends Endpoint { @Override public void onOpen(final Session session, EndpointConfig config) { session.addMessageHandler(new MessageHandler.Whole<String>() { @Override public void onMessage(String message) { try { session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } } }); } }
The functionality of the EchoEndpointProgrammatic
is fairly simple - to send the received message back to the sender.
The programmatic server endpoint needs to extend jakarta.websocket.Endpoint
- line 1.
Mehod public void onOpen(final Session session, EndpointConfig config)
gets called once new
connection to this endpoin0t is opened. In this method the MessageHandler
is registered to the
jakarta.websocket.Session
instance, which opened the connection. Method public void
onMessage(String message)
gets called once the message is received. On line 8 the message
is sent back to the sender.
To see how both annotated and programmatic endpoints may be deployed please check the section Deployment. In short: you need to put the server endpoint classes into WAR, deploy on server and the endpoints will be scanned by server and deployed.
To use Tyrus in standalone mode it is necessary to depend on correct Tyrus artifacts. The following artifacts need to be added to your pom to use Tyrus:
<dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-server</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-container-grizzly-server</artifactId> <version>2.2.0</version> </dependency>
Let's use the very same example like for Java API for WebSocket and deploy the EchoEndpointAnnotated
on the
standalone Tyrus server on the hostname "localhost", port 8025 and path "/websockets", so the endpoint
will be available at address "ws://localhost:8025/websockets/echo".
public void runServer() { Server server = new Server("localhost", 8025, "/websockets", null, EchoEndpoint.class); try { server.start(); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Please press a key to stop the server."); reader.readLine(); } catch (Exception e) { e.printStackTrace(); } finally { server.stop(); } }
mvn archetype:generate -DarchetypeArtifactId=tyrus-archetype-echo \ -DarchetypeGroupId=org.glassfish.tyrus.archetypes -DinteractiveMode=false \ -DgroupId=com.example -DartifactId=echo-service -Dpackage=com.example \ -DarchetypeVersion=2.2.0Feel free to adjust the
groupId
, package
and/or artifactId
of your new project. Alternatively, you can change it by updating the new project pom.xml once it gets generated.
Once the project generation from a Tyrus maven archetype is successfully finished, you should see the new
echo-service
project directory created in your current location. The directory contains
a standard Maven project structure:
Project build and management configuration is described in the pom.xml located
in the project root directory.
|
Project sources are located under src/main/java . |
Project resources are located under src/main/resources . |
Project web application files are located under src/main/webapp . |
The project contains the simple EchoEndpoint
class which looks
(similar to "Annotated Echo Endpoint") as follows:
package com.example;
import jakarta.websocket.OnMessage;
import jakarta.websocket.Session;
import jakarta.websocket.server.ServerEndpoint;
/**
* Server endpoint "listening" on path "/echo".
*/
@ServerEndpoint("/echo")
public class EchoEndpoint {
/**
* Method handling incoming text messages.
* <p></p>
* This implementations just sends back received message.
*
* @param message received message.
* @return returned value will be sent back to client. You can also declare this method to not return anything. In
* that case, you would need to obtain {@link jakarta.websocket.Session} object and call
* {@link jakarta.websocket.Session#getBasicRemote()#sendText();} in order to send message.
*/
@OnMessage
public String echo(String message) {
return message;
}
}
Another piece of code that has been generated in this skeleton project is a EchoTest
unit test class that is located in the same com.example
package as the
EchoEndpoint
class, however, this unit test class is placed into the maven project test source
directory src/test/java
(similar to "Client Endpoint"):
public class EchoTest extends TestContainer { public static final String MESSAGE = "Do or do not, there is no try."; @Test public void testEcho() throws DeploymentException, InterruptedException, IOException { final Server server = startServer(EchoEndpoint.class); final CountDownLatch messageLatch = new CountDownLatch(1); try { final ClientManager client = ClientManager.createClient(); client.connectToServer(new Endpoint() { @Override public void onOpen(Session session, EndpointConfig EndpointConfig) { try { session.addMessageHandler(new MessageHandler.Whole<String>() { @Override public void onMessage(String message) { System.out.println("# Received: " + message); if (message.equals(MESSAGE)) { messageLatch.countDown(); } } }); session.getBasicRemote().sendText(MESSAGE); } catch (IOException e) { // do nothing } } }, ClientEndpointConfig.Builder.create().build(), getURI(EchoEndpoint.class)); assertTrue(messageLatch.await(1, TimeUnit.SECONDS)); } catch (Exception e) { e.printStackTrace(); fail(); } finally { stopServer(server); } } }
In this unit test, a Grizzly container is first started and server application is deployed using the static call
TestContainer.startServer()
.
Next, WebSocket client instance client
is built and then a programmatic WebSocket
Endpoint
is used to connect to the EchoEndpoint
deployed on the Grizzly server
at ws://localhost:8025/e2e-test/echo
. The value of port 8025
and context path
e2e-test
are default values preset at the TestContainer
.
The generated project structure also contains an index.html
file, stored in
src/test/webapp
folder. This web page contains a javascript WebSocket client that is
capable of sending WebSocket requests to the EchoEndpoint
. This web page is deployed along
the war application in the WebSocket container such as Tyrus.
Now that we have seen the content of the project, let's try to test-run it. To do this, we need to invoke following command on the command line:
mvn clean package
This will compile the project and run the project unit tests. We should see a similar output that informs about a successful build once the build is finished:
Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] Packaging webapp [INFO] Assembling webapp [echo-service] in [.../echo-service/target/echo-service] [INFO] Processing war project [INFO] Copying webapp resources [.../echo-service/src/main/webapp] [INFO] Building war: .../echo-service/target/echo-service.war [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.979 s [INFO] Finished at: 2022-01-28T14:23:45+01:00 [INFO] Final Memory: 17M/490M [INFO] ------------------------------------------------------------------------
Now you are ready to take the packaged WAR (located under ./target/echo-service.war
)
and deploy it to a WebSocket container of your choice.
To deploy the application, you will need a WebSocket compliant container (such as Tyrus). Tyrus can be found for instance in Glassfish 7. It is also possible to create a packaged WAR with Tyrus jars included to deploy to a Servlet 5/Servlet 6 compliant container.
Once the war is deployed, it can be run on the deployment URI such as
localhost:8080/echo-service/
(the included index.html
web page can be
opened in a web browser). The web page has a title Web Socket Echo Client
and contains a
button with a label Press me
and a text field prefilled with Hello Web Sockets !
When the button is pressed, the WebSocket echo service queried and the web page contains a communication:
CONNECTED SENT: Hello Web Sockets ! RECEIVED: Hello Web Sockets !