Affects: 6.0.7
It is not clear how to configure a WebSocket server using Jetty and Spring Boot 3 / Spring framework 6. More specifically, I want to configure stuff like idleTimeout
.
The doc explained it well for Spring Boot 2 / Spring framework 5, but that doc is obsolete now that WebSocketPolicy
can't be instantiated anymore, WebSocketServerFactory
doesn't exist anymore, and JettyRequestUpgradeStrategy
has been refactored with only a no-arg constructor. So it seems like that doc needs an update.
Comment From: rstoyanchev
Good point that we need to update the documentation there. From what I can see in this example, it involves a ServletContext initialization hook. However, I'm also wondering if you've tried this in Server Configuration that now probably also works for Jetty.
Comment From: Simon3
Yes I have tried to define a ServletServerContainerFactoryBean
bean but it didn't seem to be used. But maybe I have done something wrong.
Comment From: rstoyanchev
Actually I meant have you tried what's in the example I referenced under jetty-project?
Comment From: Simon3
Not sure how to apply that in a Spring Boot environment... sorry.
Comment From: peterhalicky
I have the same problem. The example referenced by @rstoyanchev didn't help. Also when trying to use the Server Configuration, first I had to add jakarta.websocket-api 2.0.0 to classpath, because it was looking for class jakarta.websocket.WebSocketContainer that it didn't find (ClassNotFoundException). Then I got "Attribute 'jakarta.websocket.server.ServerContainer' not found in ServletContext", which -- I suppose -- means that no, this approach doesn't work on Jetty.
Comment From: djivko
For anyone finding this issue. After pouring through spring code I ended up with 2 approaches for setting the configuration for Jetty.
Approach 1:
@Bean
public JettyWebSocketServletWebServerCustomizer websocketServletWebServerCustomizer() {
return new JettyCustomizer();
}
public class JettyCustomizer extends JettyWebSocketServletWebServerCustomizer {
public JettyCustomizer() {
}
@Override
public void customize(JettyServletWebServerFactory factory) {
super.customize(factory);
factory.addConfigurations(new AbstractConfiguration() {
@Override
public void configure(WebAppContext context) throws Exception {
JettyWebSocketServerContainer container =
JettyWebSocketServerContainer.getContainer(context.getServletContext());
if (container != null) {
container.setInputBufferSize(textMessageMaxSize);
container.setMaxTextMessageSize(textMessageMaxSize);
}
}
});
}
}
This will override the customizer provided by WebSocketServletAutoConfiguration
Approach 2 using a handshake handler:
@Bean
public DefaultHandshakeHandler handshakeHandler() {
return new DefaultHandshakeHandler(new ServletContextAwareJettyRequestUpgradeStrategy());
}
public class ServletContextAwareJettyRequestUpgradeStrategy extends JettyRequestUpgradeStrategy
implements ServletContextAware {
private ServletContext servletContext;
public ServletContextAwareJettyRequestUpgradeStrategy() {
super();
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void upgrade(ServerHttpRequest request, ServerHttpResponse response, @Nullable String selectedProtocol,
List<WebSocketExtension> selectedExtensions, @Nullable Principal user,
org.springframework.web.socket.WebSocketHandler handler, Map<String, Object> attributes)
throws HandshakeFailureException {
if (servletContext != null) {
JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(servletContext);
if (container != null) {
container.setInputBufferSize(textMessageMaxSize);
container.setMaxTextMessageSize(textMessageMaxSize);
}
}
super.upgrade(request, response, selectedProtocol, selectedExtensions, user, handler, attributes);
}
}
I would love to hear somebody from Spring devs commenting whether there is more elegant way of setting the configuration.
Note: Ideally my expectation was that Spring provides a way for devs to provide a bean implementation of JettyWebSocketServletContainerInitializer.Configurator
interface that could take care of that config, but I wasn't able to find how to do that.
Comment From: rstoyanchev
Thanks for the feedback @djivko.
We can probably expose a Consumer<JettyWebSocketServerContainer>
on JettyRequestUpgradeStrategy
along the lines of your approach 2. Ideally it should be applied once on startup rather than on every upgrade. Have you tried to access it from ServletContextAware#setServletContext
?
JettyWebSocketServerContainer
seems to be initialized pretty early through a ServletContainerInitializer
, and I expect that JettyWebSocketServerContainer getContainer(servletContext)
, which relies on a ServletContext attribute might work already at that point.
Comment From: rstoyanchev
I have confirmed that JettyWebSocketServerContainer
is initialized and set in a ServletContext
attribute by the time of the ServletContextAware
callback. So I'm going to expose a Consumer<
Configurable>
property on JettyRequestUpgradeStrategy
, similar to how it was possible to configure a WebSocketPolicy
in the same place.
Unfortunately this cannot be done in 6.0.x since the Configurable
type is new in Jetty 12 but it's possible to work around it along the lines of https://github.com/spring-projects/spring-framework/issues/30344#issuecomment-1702906503.