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.
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.
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.
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.
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.
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>
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.
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
org.eclipse.jetty:jetty-server:11.0.0
org.eclipse.jetty:jetty-servlet:11.0.0
org.eclipse.jetty:jetty-annotations:11.0.0
org.eclipse.jetty.websocket:websocket-jetty-server:11.0.0
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();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With