Springboot @ServerEndPoint "Failed to find the root WebApplicationContext."
Asked Answered
B

3

5

I'm having trouble using spring with @ServerEndPoint annotated class

i'm using Springboot 1.2.3 and i'm trying to figure it out how to have a single instance of the endpoint

@SpringBootApplication
@EnableJpaRepositories
@EnableWebSocket
public class ApplicationServer {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationServer.class, args);
    }
}

Spring configuration:

@ConditionalOnWebApplication
@Configuration
public class WebSocketConfigurator {

    @Bean
    public ServerEndPoint serverEndpoint() {
        return new ServerEndPoint();
    }

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

WebSocket endpoint:

@ServerEndpoint(value = "/", decoders = MessageDecoder.class, 
encoders = MessageEncoder.class, configurator = SpringConfigurator.class)
public class ServerEndPoint {

    private static final Logger LOG = LoggerFactory.getLogger(ServerEndPoint.class);

    public static final Set<CommunicationObserver> OBSERVERS = Sets.newConcurrentHashSet();

    @OnMessage
    public void onMessage(Session session, Message msg) {
        LOG.debug("Received msg {} from {}", msg, session.getId());
        for (CommunicationObserver o : OBSERVERS) {
            o.packetReceived(session, msg);
        }
    }

This is based on the Spring WebSocket JSR-356 tutorial, but i've got the following error:

java.lang.IllegalStateException: Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?
    at org.springframework.web.socket.server.standard.SpringConfigurator.getEndpointInstance(SpringConfigurator.java:68)
    at org.apache.tomcat.websocket.pojo.PojoEndpointServer.onOpen(PojoEndpointServer.java:50)
    at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.init(WsHttpUpgradeHandler.java:138)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:687)
    at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1558)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1515)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)

I have both tested in embedded mode and with external tomcat 8 and jetty 9 (in external mode, i remove de Spring config file) but the same error appeares.

the only workaround i've found is to create a custom configurator.

public class SpringEndpointConfigurator extends ServerEndpointConfig.Configurator {

    private static WebApplicationContext wac;

    public SpringEndpointConfigurator() {
    }

    public SpringEndpointConfigurator(WebApplicationContext wac) {
        SpringEndpointConfigurator.wac = wac;
    }

    @Override
    public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
        T endPoint = wac.getAutowireCapableBeanFactory().getBean(endpointClass);
        return (endPoint != null) ? endPoint : wac.getAutowireCapableBeanFactory().createBean(endpointClass);
    }

it is created as a @Bean with the parameterized constructor.

I must have missed something to get it done with the SpringConfigurator class, but i don't know what.

Borne answered 27/5, 2015 at 12:53 Comment(3)
Instead of following that tutorial I suggest this one as that is tailored for Spring Boot instead of plain spring.Fondea
this tutorial is using STOMP protocol, i have to stick with plaintext websocket because we use a custom protocol. or am i missing something ?Borne
might help: #27158606Information
B
1

thanks to Sergi Almar and his answer, i have managed to use the Spring implementation instead of the javax.websocket implementation:

public class SpringWebSocketHandler extends TextWebSocketHandler {

    private final Set<CommunicationObserver> observers = Sets.newConcurrentHashSet();

    @Autowired
    private MessageContext mc;

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        Message msg = mc.parse(message.getPayload());

        for (CommunicationObserver o : observers) {
            o.packetReceived(session, msg);
        }
    }
}

Config file:

@ConditionalOnWebApplication
@Configuration
public class WebSocketConfigurator implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/").setAllowedOrigins("*");
    }

    @Bean
    public SpringWebSocketHandler myHandler() {
        return new SpringWebSocketHandler();
    }
}

Note that the setAllowedOrigins("*") was mandatory for me because when using the java client, i had the following error:

org.springframework.web.util.WebUtils : Failed to parse Origin header value [localhost:8080]

Also note that the MessageContext was used to parse/format strings instead of MessageEncoder/Decoder classes (they inherited from MessageContext).

Borne answered 28/5, 2015 at 13:2 Comment(0)
A
9

SpringConfigurator uses ContextLoader to obtain spring context. Spring Boot does set up the ServletContext but it never uses ContextLoaderListener which initializes ContextLoader to hold static state of spring context. You can try to add ContextLoaderListener or as a workaround you can write your own context holder and configurator.

Here is an example:

First context holder and configurator:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import javax.websocket.server.ServerEndpointConfig;

public class CustomSpringConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {

    /**
     * Spring application context.
     */
    private static volatile BeanFactory context;

    @Override
    public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
        return context.getBean(clazz);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        CustomSpringConfigurator.context = applicationContext;
    }
}

To get context we need to configure this as Bean:

@ConditionalOnWebApplication
@Configuration
public class WebSocketConfigurator {

...

    @Bean
    public CustomSpringConfigurator customSpringConfigurator() {
        return new CustomSpringConfigurator(); // This is just to get context
    }
}

Then you need to set configurator correctly:

@ServerEndpoint(value = "/", decoders = MessageDecoder.class, 
encoders = MessageEncoder.class, configurator = CustomSpringConfigurator.class)
public class ServerEndPoint {
...
}

As a side note, yes if you delete SpringConfigurator your application will start and you can handle request. But you cannot autowire other beans.

Accomplish answered 13/8, 2016 at 18:58 Comment(1)
I have no idea what you are doing, but thanks a lot, it works for meParamedic
B
1

thanks to Sergi Almar and his answer, i have managed to use the Spring implementation instead of the javax.websocket implementation:

public class SpringWebSocketHandler extends TextWebSocketHandler {

    private final Set<CommunicationObserver> observers = Sets.newConcurrentHashSet();

    @Autowired
    private MessageContext mc;

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        Message msg = mc.parse(message.getPayload());

        for (CommunicationObserver o : observers) {
            o.packetReceived(session, msg);
        }
    }
}

Config file:

@ConditionalOnWebApplication
@Configuration
public class WebSocketConfigurator implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/").setAllowedOrigins("*");
    }

    @Bean
    public SpringWebSocketHandler myHandler() {
        return new SpringWebSocketHandler();
    }
}

Note that the setAllowedOrigins("*") was mandatory for me because when using the java client, i had the following error:

org.springframework.web.util.WebUtils : Failed to parse Origin header value [localhost:8080]

Also note that the MessageContext was used to parse/format strings instead of MessageEncoder/Decoder classes (they inherited from MessageContext).

Borne answered 28/5, 2015 at 13:2 Comment(0)
E
1

With Spring Boot the Spring context is not loaded via SpringContextLoaderListener which is required by the SpringConfigurator.class.

Thats why the endpoints and and ServerEndpointExporter beans are needed.

The only thing you have to do to get your example working is removing the SpringConfigurator.class from the @ServerEndPoint endpoint definition.

Elisabethelisabethville answered 2/7, 2015 at 11:35 Comment(2)
thanks for the reply but what i wanted was to have a single instance of the ServerEndPoint. With the ServerEndpointExporter, there is a new instance on every incoming socket connexion.Borne
how the endpoint is registered in ServerEndpointExporter and how to use it... I not understand about that in docs.spring.io/spring-framework/docs/current/javadoc-api/org/… . Would you kindly give me example.Mccarver

© 2022 - 2024 — McMap. All rights reserved.