Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set up websockets with Jetty 11

I am trying to migrate from Jetty 9.4 to Jetty 11 (maybe too early?) and failing in adapting the code for setting up websockets. The way I achieved this in 9.4 was as follows:

Server server = new Server();
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSendServerVersion(false);
HttpConnectionFactory httpFactory = new HttpConnectionFactory(httpConfig);
ServerConnector httpConnector = new ServerConnector(server, httpFactory);
httpConnector.setPort(port);
server.setConnectors(new Connector[] { httpConnector });

// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");

// Add a websocket to a specific path spec
ServletHolder holderEvents2 = new ServletHolder("websocket", EventsServlet.class);
context.addServlet(holderEvents2, "/events/*");

HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] { context, new DefaultHandler() });

server.setHandler(handlers);

public class EventsServlet extends WebSocketServlet {

    @Override
    public void configure(WebSocketServletFactory factory) {
        // register a socket class as default
        factory.register(EchoSocket.class);
    }
}

public class EchoSocket implements WebSocketListener {
    // ...
}

As there is no WebSocketServlet class anymore, I fiddled around a bit and found the class JettyWebSocketServlet. According to its JavaDoc, I thought it should like as follows:

public class EventsServlet extends JettyWebSocketServlet {

    @Override
    protected void configure(JettyWebSocketServletFactory factory) {
        // register a socket class as default
//      factory.register(EchoSocket.class);
           factory.addMapping("/", (req,res)->new EchoSocket());

    }
}

but the line with addMapping is actually never executed. Also JettyWebSocketServletFactory does not have a method called setDefaultMaxFrameSize as suggested by the JavaDoc of JettyWebSocketServlet .

All I seem to be able to find on the web is for Jetty <= 9.4, even https://github.com/jetty-project/embedded-jetty-websocket-examples .

Any help would be highly appreciated.

like image 323
naf Avatar asked Dec 24 '20 21:12

naf


People also ask

How do you implement Websockets?

How Websockets are implemented? webSockets are implemented as follows: Client makes HTTP request to server with "upgrade" header on the request. If server agrees to the upgrade, then client and server exchange some security credentials and the protocol on the existing TCP socket is switched from HTTP to webSocket.

How do I connect my Android to Websockets?

Use the ws://localhost: address followed by your Port Number (e.g. ws://localhost:9998 ), to connect to the WebSocket server and communicate using Streams API messages.

How do I run a WebSocket on a server?

To open a websocket connection, we need to create new WebSocket using the special protocol ws in the url: let socket = new WebSocket("ws://javascript.info"); There's also encrypted wss:// protocol. It's like HTTPS for websockets.

How do I connect to a WebSocket endpoint?

In order to communicate using the WebSocket protocol, you need to create a WebSocket object; this will automatically attempt to open the connection to the server. The URL to which to connect; this should be the URL to which the WebSocket server will respond.


3 Answers

I had a similar problem, though my version running under Jetty 9.4 was a bit different to yours, using WebSocketHandler rather than WebSocketServlet. I was having some problems with the old approach, since under Jetty 9.4 I had to pass my listener class as a Class object, which makes dependency injection a pain.

I have now got this working under Jetty 11.0.0 though. I found your question a couple of days ago while I was trying to work out how to do this in Jetty 11, and it inspired me to actually get this working, so thanks!

FWIW, my Jetty 9.4 version (for a trivial test) looked like this:

public static void main(String[] argv) throws Exception
{
    int serverPort = Integer.getInteger("server.port", 8080);

    Server server = new Server(serverPort);
    ContextHandlerCollection handlers = new ContextHandlerCollection();

    WebSocketHandler wsh = new WebSocketHandler.Simple (TestWebSocketListener.class);
    handlers.addHandler(createContextHandler("/ws", wsh));

    ResourceHandler rh = new ResourceHandler();
    rh.setDirectoriesListed(false);
    rh.setBaseResource(Resource.newClassPathResource("/WEB-STATIC/"));
    handlers.addHandler(createContextHandler("/", rh));

    server.setHandler(handlers);

    server.start();
    server.join();
}

// Convenience method to create and configure a ContextHandler.
private static ContextHandler createContextHandler(String contextPath, Handler wrappedHandler)
{
    ContextHandler ch = new ContextHandler (contextPath);
    ch.setHandler(wrappedHandler);
    ch.clearAliasChecks();
    ch.setAllowNullPathInfo(true);
    return ch;
}

Here, TestWebSocketListener is a trivial implementation of WebSocketListener which just implements each listener method and prints the arguments to System.err. (I did say this was a trivial test.) I also send a message back to the client in the onWebSocketText callback, just to check that this works.

I'm not using DefaultHandler here - instead, I explicitly create a ResourceHandler which serves a few simple static resources from a resource tree stored within the classpath (under the /WEB-STATIC/ prefix).

The version I have working under Jetty 11.0.0 just changes the main method above to this:

public static void main(String[] argv) throws Exception
{
    int serverPort = Integer.getInteger("server.port", 8080);

    Server server = new Server(serverPort);
    ContextHandlerCollection handlers = new ContextHandlerCollection();

    ResourceHandler rh = new ResourceHandler();
    rh.setDirectoriesListed(false);
    rh.setBaseResource(Resource.newClassPathResource("/WEB-STATIC/"));
    handlers.addHandler(createContextHandler("/", rh));

    Servlet websocketServlet = new JettyWebSocketServlet() {
        @Override protected void configure(JettyWebSocketServletFactory factory) {
            factory.addMapping("/", (req, res) -> new TestWebSocketListener());
        }
    };
    ServletContextHandler servletContextHandler = new ServletContextHandler();
    servletContextHandler.addServlet(new ServletHolder(websocketServlet), "/ws");
    JettyWebSocketServletContainerInitializer.configure(servletContextHandler, null);
    handlers.addHandler(servletContextHandler);

    server.setHandler(handlers);

    server.start();
    server.join();
}

The call to JettyWebSocketServletContainerInitializer.configure is important: without that I got exceptions complaining that the WebSocket components had not been initialised.

One thing to note is that the order of the two handlers has been changed - previously, the WebSocketHandler was added before the ResourceHandler. However, when using ServletContextHandler this was returning 404s for paths that should have been handled by the ResourceHandler, so I swapped the order.

The TestWebSocketListener is identical between the two versions. Obviously, it's a lot easier for me to add dependency injection now I control the constructor call!

The other thing I had to change was the names of the Maven artifacts I pulled in. The websocket-server artifact no longer seems to exist in Jetty 11, so I changed this:

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-server</artifactId>
    <version>9.4.35.v20201120</version>
</dependency>
<dependency>
    <groupId>org.eclipse.jetty.websocket</groupId>
    <artifactId>websocket-server</artifactId>
    <version>9.4.35.v20201120</version>
</dependency>

to this:

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-server</artifactId>
    <version>11.0.0</version>
</dependency>
<dependency>
    <groupId>org.eclipse.jetty.websocket</groupId>
    <artifactId>websocket-jetty-server</artifactId>
    <version>11.0.0</version>
</dependency>
like image 184
mdf Avatar answered Nov 14 '22 22:11

mdf


Thanks to the detailed explanation by mdf, I was able to fix my code. In the end, I only had to replace

ServletHolder holderEvents = new ServletHolder("websocket", EventsServlet.class);
context.addServlet(holderEvents, "/events/*");

with

Servlet websocketServlet = new JettyWebSocketServlet() {
    @Override
    protected void configure(JettyWebSocketServletFactory factory) {
        factory.addMapping("/", (req, res) -> new EchoSocket());
    }
};
context.addServlet(new ServletHolder(websocketServlet), "/events/*");
JettyWebSocketServletContainerInitializer.configure(context, null);

With this I could also get rid of the EventsServlet class.

like image 28
naf Avatar answered Nov 14 '22 22:11

naf


You can find info in the Jetty 11 examples https://github.com/jetty-project/embedded-jetty-websocket-examples/blob/10.0.x/native-jetty-websocket-example/src/main/java/org/eclipse/jetty/demo/EventServer.java

Upgraded from jetty 9 with the following packages

  1. org.eclipse.jetty:jetty-server:11.0.0
  2. org.eclipse.jetty:jetty-servlet:11.0.0
  3. org.eclipse.jetty:jetty-annotations:11.0.0
  4. org.eclipse.jetty.websocket:websocket-jetty-server:11.0.0
  5. org.eclipse.jetty.websocket:websocket-jetty-client:11.0.0

Notice that the names of the websocket-client and websocket-server changed to websocket-jetty-client and websocket-jetty-server

as pointed by @mdf JettyWebSocketServletContainerInitializer.configure enables to get rid of the following message:

WebSocketComponents has not been created

Now my app works with jetty 11.

this is my WebsocketServlet

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

public class KernelServlet extends JettyWebSocketServlet {
    @Override
    public void configure(JettyWebSocketServletFactory factory) {
        factory.register(KernelHandler.class);
    }
}

and this is the server init code

Server server = new Server(port);
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setContextPath("/");
contextHandler.addServlet(WebClientServlet.class, "/client");
contextHandler.addServlet(KernelServlet.class, "/kernel");
JettyWebSocketServletContainerInitializer.configure(contextHandler, null);
try {
    server.setHandler(contextHandler);
    server.start();
    server.join();
} catch (Exception e) {
    e.printStackTrace();
}
like image 27
saulpalv Avatar answered Nov 14 '22 21:11

saulpalv