David Tran
David Tran

Reputation: 325

Convert Spring Websocket to Jetty Websocket

I'm starting to move my existing Jetty 9 application webapp to Spring Boot, but I can't seem to figure out how to use a Jetty WebSocketHandler as opposed to a Spring WebSocketHandler and WebSocketSession.

I can properly set up the Spring WebSocketHandler in my Application.java, and I found the JettyWebSocketHandlerAdaptor and the JettyWebSocketSession classes in the Spring Framework Documentation, but I haven't been able to find out how to use it or any good examples.

http://docs.spring.io/autorepo/docs/spring-framework/4.1.9.RELEASE/javadoc-api/org/springframework/web/socket/adapter/jetty/JettyWebSocketHandlerAdapter.html

How do I hand over the WebSocketHandler to Jetty? Since the JettyWebSocketAdapter doesn't derive from anything but Object, I've been trying to register a Spring WebSocketHandler and then just have all of its methods pass themselves to a JettyWebSocketHandler. Is it silly of me to be doing this:

Application.java

@EnableWebSocket
@SpringBootApplication
public class Application extends SpringBootServletInitializer implements WebSocketConfigurer {



    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        SpringWSHandler springPlease = new SpringWSHandler();


        registry.addHandler(springPlease, "/websocket").setHandshakeHandler(handshaker());
    }

    @Bean
    public DefaultHandshakeHandler handshaker()
    {
        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
        policy.setInputBufferSize(8192);
        policy.setIdleTimeout(600000);

        return new DefaultHandshakeHandler(new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
    }


    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);

    }
}

Spring WebSocketHandler

@Component
public class SpringWSHandler implements WebSocketHandler {

    private JettyHandler jettyHandler;
    private JettyWebSocketSession jettySession;

    private static Logger logger = LoggerFactory.getLogger(SpringWSHandler.class);



    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        logger.debug("Connection Established: " + session.getId());
        jettySession = new JettyWebSocketSession(session.getAttributes());
        jettyHandler = new JettyHandler(this, jettySession);
        jettyHandler.onOpen(jettySession.getNativeSession());

    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        logger.debug("Message from session: " + session.getId());
        jettyHandler.onMessage(message.getPayload().toString());

    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        //TODO

    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        logger.debug("Closing the session: " + session.getId());
        jettyHandler.onClose(closeStatus.getCode(), "Closing Connection");
        session.close(closeStatus);
    }

    @Override
    public boolean supportsPartialMessages() {
        return true;
    }

}

It fails regardless since after I open up my webpage the websocket connection seems to close whenever I try to use it. The connection closes once I attempt to use jettyHandler's methods, the exception is a ExceptionWebSocketHandlerDecorator exception:

11:44:27.530 [qtp1014565006-19] ERROR o.s.w.s.h.ExceptionWebSocketHandlerDecorator - Unhandled error for ExceptionWebSocketHandlerDecorator [delegate=LoggingWebSocketHandlerDecorator [delegate=org.appcad.webserver.jetty.SpringWSHandler@5a7b309b]]
java.lang.NullPointerException: null

Upvotes: 1

Views: 4325

Answers (2)

David Tran
David Tran

Reputation: 325

I've managed to hack together something that works for the time being, but would love to hear a better and more proper method to use Jetty WebSockets with Spring Boot.

This lets me pass messages from the Spring WebSocket to my JettyWebSocketHandlerAdapter (JettyHandler) class which passes the message to my Jetty WebSocket and associated classes.

WebSocketHandler setup in Application.java

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {        
        SpringWSHandler springPlease = new SpringWSHandler();

        registry.addHandler(springPlease, "/websocket").setHandshakeHandler(factoryBean());
    }

    //Configure buffer size and timeouts
    @Bean
    public HandshakeHandler factoryBean()
    {
        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
        policy.setInputBufferSize(8192);
        policy.setIdleTimeout(3600000);
        WebSocketServerFactory factory = new WebSocketServerFactory(policy);
        return new DefaultHandshakeHandler(new JettyRequestUpgradeStrategy(factory));
    }

SpringWSHandler.java

@Component
public class SpringWSHandler implements WebSocketHandler {

    private JettyHandler jettyHandler;
    private JettyWebSocketSession jettySession;

    private static Logger logger = LoggerFactory.getLogger(SpringWSHandler.class);



    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        //Application.java adapts WebSocket connections to Jetty 9 API when registering handlers with the Jetty handshaker
        //We can cast spring WebSocketSessions to JettyWebSocketSessions, and initialize the org.eclipse.jetty.websocket.api.Session with itself.
        jettySession = (JettyWebSocketSession) session;
        jettySession.initializeNativeSession(jettySession.getNativeSession());

        //Setup our custom handler and populate the router, which was magically created by Spring using our annotated classes in Router and its dependencies.       
        jettyHandler = new ClothoJettyHandler(this, jettySession);

        //Let the jetty web socket commence!
        jettyHandler.onOpen(jettySession.getNativeSession());

    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        logger.debug("Message from session: " + session.getId());
        jettyHandler.onMessage(message.getPayload().toString());

    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        //TODO

    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        logger.debug("Closing the session: " + session.getId());
        jettyHandler.onClose(closeStatus.getCode(), "Closing Connection");
        session.close(closeStatus);
    }

    @Override
    public boolean supportsPartialMessages() {
        return true;
    }

}

Upvotes: 1

Joakim Erdfelt
Joakim Erdfelt

Reputation: 49452

Either use Spring's WebSocketHandler or Jetty's WebSocketHandler directly, don't try to mix them like that.

You can't use the Jetty WebSocketHandler in that fashion, as it needs to be part of the Jetty LifeCycle hierarchy, and have access to the Jetty Server to be sane.

It would also be difficult to have a proper Jetty WebSocketHandler access the Spring layer, as the LifeCycle between Jetty and Spring don't mesh like that.

Perhaps you can use the WebSocketUpgradeFilter and a custom implementation of WebSocketCreator to initialize the WebSocket you are aiming for, complete with access to the Spring layers after the fact.

Your custom WebSocketCreator will be called each time a new incoming websocket upgrade occurs.

Upvotes: 1

Related Questions