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.