Search code examples
javaeclipsewebsocketjettyjava-websocket

Cannot connect to Jetty WebSocket v11


I am trying to implement server, as java plain application, where clients could connect to a web socket.

The goal is to connect client to a websocket via this url: ws://localhost:4550/api/myWebSocket

, and have some output from MyWebSocketHandler.

This is App class:

package com.main;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

public class App {
  public static void main(String[] args) {
    Server server = new Server(4550);
    ServletContextHandler context = new 
    ServletContextHandler(ServletContextHandler.SESSIONS);
    context.setContextPath("/api");
    server.setHandler(context);
    
    ServletHolder wsHolder = new ServletHolder(MyWebSocketServlet.class);
    context.addServlet(wsHolder, "/");
    
    try {
        server.start(); 
        server.join();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        server.destroy();
    }
  }
}

This is MyWebSocketServlet class:

package com.main;

import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;

import jakarta.servlet.annotation.WebServlet;

@WebServlet(urlPatterns = "/myWebSocket")
public class MyWebSocketServlet extends JettyWebSocketServlet {
    @Override
    protected void configure(JettyWebSocketServletFactory factory) {
        factory.register(MyWebSocketHandler.class);
    }
}

And lastly MyWebSocketHandler class:

package com.main;

import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;

@WebSocket
public class MyWebSocketHandler {
    @OnWebSocketConnect
    public void onConnect(Session session) {
        System.out.println("Connected: " + session.getRemoteAddress());
    }

    @OnWebSocketMessage
    public void onMessage(Session session, String message) {
        System.out.println("Message received: " + message);
        try {
            session.getRemote().sendString("Echo: " + message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @OnWebSocketClose
    public void onClose(Session session, int statusCode, String reason) {
        System.out.println("Closed: " + reason);
    }
}

In gradle build file the following libraries are imported:

implementation 'org.eclipse.jetty:jetty-server:11.0.7'
implementation 'org.eclipse.jetty:jetty-servlet:11.0.7'
implementation 'org.eclipse.jetty.websocket:websocket-jetty-server:11.0.7'

It is required to be Java Application (no Spring Boot or other framework) and (if possible) Jetty library for defining the WebSocket.


Solution

  • ws-server

    Project Tree

    ws-server
    ├── pom.xml
    ├── build.gradle
    ├── build.gradle.kts
    └── src
        └── main
            └── java
                └── com
                    └── example
                        └── wsserver
                            ├── HelloWebSocketServerEndpoint.java
                            └── Main.java
    

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <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>com.example</groupId>
      <artifactId>websocket-server-jakarta-api</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>Jetty Examples :: Jetty 11.0.x :: Embedded :: WebSocket Server with Jakarta EE API</name>
    
      <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <jetty.version>11.0.24</jetty.version>
      </properties>
      
      <dependencies>
      
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-server</artifactId>
          <version>${jetty.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-servlet</artifactId>
          <version>${jetty.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-slf4j-impl</artifactId>
          <version>${jetty.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.eclipse.jetty.websocket</groupId>
          <artifactId>websocket-jakarta-server</artifactId>
          <version>${jetty.version}</version>
        </dependency>
    
      </dependencies>
      <build>
          <finalName>websocket-server</finalName>
      </build>
    </project>
    

    build.gradle

    Choose one between build.gradle and build.gradle.kts.

    plugins {
        id 'java'
        id 'application'
    }
    
    group = 'com.example'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '17'
    targetCompatibility = '17'
    
    mainClassName = 'com.example.wsserver.Main'
    
    
    ext {
        jettyVersion = '11.0.24'
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation "org.eclipse.jetty:jetty-server:$jettyVersion"
        implementation "org.eclipse.jetty:jetty-servlet:$jettyVersion"
        implementation "org.eclipse.jetty:jetty-slf4j-impl:$jettyVersion"
        implementation "org.eclipse.jetty.websocket:websocket-jakarta-server:$jettyVersion"
    }
    
    tasks.withType(JavaCompile) {
        options.encoding = 'UTF-8'
    }
    
    jar {
        archiveFileName = "websocket-server.jar"
    }
    

    build.gradle.kts

    Choose one between build.gradle and build.gradle.kts.

    plugins {
        java
        application
    }
    
    group = "com.example"
    version = "0.0.1-SNAPSHOT"
    
    application {
        mainClass.set("com.example.wsserver.Main")
    }
    
    java {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    
    val jettyVersion = "11.0.24"
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation("org.eclipse.jetty:jetty-server:$jettyVersion")
        implementation("org.eclipse.jetty:jetty-servlet:$jettyVersion")
        implementation("org.eclipse.jetty:jetty-slf4j-impl:$jettyVersion")
        implementation("org.eclipse.jetty.websocket:websocket-jakarta-server:$jettyVersion")
    }
    
    tasks.withType<JavaCompile> {
        options.encoding = "UTF-8"
    }
    
    tasks.jar {
        archiveFileName.set("websocket-server.jar")
    }
    

    Main.java

    package com.example.wsserver;
    
    import jakarta.websocket.server.ServerEndpointConfig;
    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.servlet.ServletContextHandler;
    import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class Main
    {
        private static final Logger LOG = LoggerFactory.getLogger(Main.class);
    
        public static void main(String[] args) throws Exception
        {
            Server server = newServer(4550);
            LOG.info("new Server 4550");
            server.start();
            LOG.info("server.start()");
            server.join();
            LOG.info("server.join()");
        }
    
        public static Server newServer(int port)
        {
            Server server = new Server(port);
    
            ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
            servletContextHandler.setContextPath("/api");
            server.setHandler(servletContextHandler);
    
            // Add jakarta.websocket support
            JakartaWebSocketServletContainerInitializer.configure(servletContextHandler, (context, container) ->
            {
                // Add echo endpoint to server container
                ServerEndpointConfig echoConfig = ServerEndpointConfig.Builder.create(HelloWebSocketServerEndpoint.class, "/myWebSocket").build();
                container.addEndpoint(echoConfig);
            });
    
            return server;
        }
    }
    

    HelloWebSocketServerEndpoint.java

    package com.example.wsserver;
    
    import jakarta.websocket.*;
    import jakarta.websocket.server.ServerEndpoint;
    import java.io.IOException;
    import java.util.concurrent.CopyOnWriteArraySet;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    @ServerEndpoint("/websocket")
    public class HelloWebSocketServerEndpoint {
        private static final Logger LOG = LoggerFactory.getLogger(HelloWebSocketServerEndpoint.class);
        // Stores all connected client sessions
        private static final CopyOnWriteArraySet<Session> sessions = new CopyOnWriteArraySet<>();
    
        // Called when a new client connects
        @OnOpen
        public void onOpen(Session session) {
            sessions.add(session);
            LOG.info("WebSocket opened: " + session.getId());
            broadcastMessage("Client " + session.getId() + " joined!");
        }
    
        // Called when a client message is received
        @OnMessage
        public void onMessage(String message, Session session) {
            LOG.info("Message received from " + session.getId() +": " + message);
            try {
                session.getBasicRemote().sendText("Echo: " + message);
            } catch (IOException e) {
                LOG.warn("IOException: {}",e);
            }
        }
    
        // Called when the client disconnects
        @OnClose
        public void onClose(Session session, CloseReason reason) {
            sessions.remove(session);
            LOG.info("WebSocket closed: " + session.getId());
            broadcastMessage("Client " + session.getId() + " left.");
        }
    
        // Called when an error occurs
        @OnError
        public void onError(Session session, Throwable throwable) {
            LOG.error("WebSocket error: " + session.getId());
            throwable.printStackTrace();
        }
    
        // Broadcast a message to all clients
        private void broadcastMessage(String message) {
            LOG.info("broadcast >>>>> {}",message);
            for (Session session : sessions) {
                try {
                    session.getBasicRemote().sendText("broadcast >>>>> "+message);
                } catch (IOException e) {
                    LOG.warn("IOException: {}",e);
                }
            }
        }
    }
    

    Maven - Build and Run

    Linux Command:

    Build

    mvn clean package
    

    Download dependencies jars

    Download dependencies jar files into target/libs

    mvn dependency:copy-dependencies -DoutputDirectory=target/libs
    

    Run

    java -cp "target/libs/*:target/websocket-server.jar" com.example.wsserver.Main
    

    Gradle - Build and Run

    Linux Command:

    Build

    gradle clean build
    

    Run

    unzip distributions zip

    cd build/distributions
    unzip ws-server-0.0.1-SNAPSHOT.zip
    

    Run

    # build/distributions/ws-server-0.0.1-SNAPSHOT/bin
    
    cd ws-server-0.0.1-SNAPSHOT/bin
    ./ws-server
    

    ws-client

    Project Tree

    ws-client
    ├── pom.xml
    ├── build.gradle
    ├── build.gradle.kts
    └── src
        └── main
            └── java
                └── com
                    └── example
                        └── wsclient
                            ├── HelloWebsocketClientEndpoint.java
                            └── Main.java
    
    

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <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>com.example</groupId>
      <artifactId>websocket-client-jakarta-api</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>Jetty Examples :: Jetty 11.0.x :: Embedded :: WebSocket Client with Jakarta EE API</name>
    
      <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <jetty.version>11.0.24</jetty.version>
      </properties>
      
      <dependencies>
    
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-slf4j-impl</artifactId>
          <version>${jetty.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.eclipse.jetty.websocket</groupId>
          <artifactId>websocket-jakarta-client</artifactId>
          <version>${jetty.version}</version>
        </dependency>
    
      </dependencies>
      
      <build>
          <finalName>websocket-client</finalName>
      </build>
    </project>
    

    build.gradle

    Choose one between build.gradle and build.gradle.kts.

    plugins {
        id 'java'
        id 'application'
    }
    
    group = 'com.example'
    version = '0.0.1-SNAPSHOT'
    
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
    
    mainClassName = 'com.example.wsclient.Main'
    
    repositories {
        mavenCentral()
    }
    
    ext {
        jettyVersion = '11.0.24'
    }
    
    dependencies {
        implementation "org.eclipse.jetty:jetty-slf4j-impl:$jettyVersion"
        implementation "org.eclipse.jetty.websocket:websocket-jakarta-client:$jettyVersion"
    }
    
    jar {
        archiveBaseName.set("websocket-client")
    }
    
    tasks.withType(JavaCompile) {
        options.encoding = 'UTF-8'
    }
    
    

    build.gradle.kts

    Choose one between build.gradle and build.gradle.kts.

    plugins {
        java
        application    
    }
    
    group = "com.example"
    version = "0.0.1-SNAPSHOT"
    
    application {
        mainClass.set("com.example.wsclient.Main")
    }
    
    java {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    
    repositories {
        mavenCentral()
    }
    
    val jettyVersion = "11.0.24"
    
    dependencies {
        implementation("org.eclipse.jetty:jetty-slf4j-impl:$jettyVersion")
        implementation("org.eclipse.jetty.websocket:websocket-jakarta-client:$jettyVersion")
    }
    
    tasks.jar {
        archiveBaseName.set("websocket-client")
    }
    
    tasks.withType<JavaCompile> {
        options.encoding = "UTF-8"
    }
    
    

    Main.java

    package com.example.wsclient;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    public class Main {
        private static final Logger LOG = LoggerFactory.getLogger(Main.class);
    
        public static void main(String[] args) {
            String serverUri = "ws://localhost:4550/api/myWebSocket";
            LOG.info("serverUri: {}", serverUri);
            HelloWebsocketClientEndpoint client = new HelloWebsocketClientEndpoint(serverUri);
    
            // Send a message to the server
            client.sendMessage("Hello from client at " + java.util.Calendar.getInstance().getTime() );
    
            // Wait 5 seconds for a response
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                //e.printStackTrace();
                LOG.warn("exception: {}",e);
            }
    
            // Close the client
            client.close();
            LOG.warn("client.close()");
        }
    
    }
    

    HelloWebSocketServerEndpoint.java

    package com.example.wsclient;
    
    import jakarta.websocket.*;
    import java.net.URI;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    @ClientEndpoint
    public class HelloWebsocketClientEndpoint {
        private static final Logger LOG = LoggerFactory.getLogger(HelloWebsocketClientEndpoint.class);
        private Session session;
    
        public HelloWebsocketClientEndpoint(String serverUri) {
            try {
                WebSocketContainer container = ContainerProvider.getWebSocketContainer();
                session = container.connectToServer(this, new URI(serverUri));
            } catch (Exception e) {
                LOG.warn("exception: {}",e);
            }
        }
    
        @OnMessage
        public void onMessage(String message) {
            LOG.info("Received from server: {}" , message);
        }
    
        public void sendMessage(String message) {
            LOG.info("sendMessage: {}" , message);
            try {
                if (session != null && session.isOpen()) {
                    session.getAsyncRemote().sendText(message);
                } else {
                    LOG.info("Session is not open");
                }
            } catch (Exception e) {
                LOG.warn("exception: {}",e);
            }
        }
    
        public void close() {
            try {
                if (session != null) {
                    session.close();
                    LOG.info("close()");
                }
            } catch (Exception e) {
                LOG.warn("exception: {}",e);
            }
    
        }
    
    }
    

    Maven - Build and Run

    Linux Command:

    Build

    mvn clean package
    

    Download dependencies jars

    Download dependencies jar files into target/libs

    mvn dependency:copy-dependencies -DoutputDirectory=target/libs
    

    Run

    Execute 10 WebSocketClient connections to WebSocketServer simultaneously.

    for i in {1..10}; do
        java -cp "target/libs/*:target/websocket-client.jar" com.example.wsclient.Main &
    done
    

    Execute 1 WebSocketClient connections to WebSocketServer.

    java -cp "target/libs/*:target/websocket-client.jar" com.example.wsclient.Main
    

    Gradle - Build and Run

    Linux Command:

    Build

    gradle clean build
    

    Run

    unzip distributions zip

    cd build/distributions
    unzip ws-client-0.0.1-SNAPSHOT.zip
    

    Run

    # build/distributions/ws-client-0.0.1-SNAPSHOT/bin
    
    cd ws-client-0.0.1-SNAPSHOT/bin
    ./ws-client
    

    Execute 10 WebSocketClient connections to WebSocketServer simultaneously.

    for i in {1..10}; do
        ./ws-client &
    done
    

    Note

    Jetty websocket examples have 2 examples:

    • websocket-jakarta-api
    • websocket-jetty-api

    This answer is based on websocket-jakarta-api.

    https://github.com/jetty/jetty-examples/blob/11.0.x/embedded/websocket-jakarta-api/pom.xml