Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring WebFlux and WebSocket

I am trying to add WebSocket functionality to an existing application which uses Spring WebFlux. It uses:

  • Spring boot 2.2.1.RELEASE
  • Tomcat container
  • Configured to serve jsp pages

When I try to connect to it through JavaScript (from inside a jsp page) I am receiving the error "failed: Error during WebSocket handshake: Unexpected response code: 404"

I noted that if I create a controller class mapping url '/ws/event-emitter' (the one mapped in my websocket configuration) accepting GET requests, it receives the requests from Javascript call when it tries to open the websocket connection, which made me more confused.

What am I missing?

After read some tutorials on the Internet I set my configuration as below:

<?xml version="1.0" encoding="UTF-8"?>

4.0.0 org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE br.com.samsung monitor 0.0.1-SNAPSHOT monitor Health Check Monitor

<properties>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- 
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
     -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.projectreactor</groupId>
        <artifactId>reactor-spring</artifactId>
        <version>1.0.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.microsoft.sqlserver</groupId>
        <artifactId>mssql-jdbc</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- MongoDB -->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-mongodb</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-releasetrain</artifactId>
        <version>Lovelace-SR9</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
@Configuration
public class ReactiveWebSocketConfiguration {

    @Autowired
    private WebSocketHandler webSocketHandler;

    @Bean
    public HandlerMapping webSocketHandlerMapping() {
        Map<String, WebSocketHandler> map = new HashMap<>();
        map.put("/ws/event-emitter", webSocketHandler);

        SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
        handlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
        handlerMapping.setUrlMap(map);
        return handlerMapping;
    }

    @Bean
    public WebSocketHandlerAdapter handlerAdapter(WebSocketService webSocketService) {
        return new WebSocketHandlerAdapter(webSocketService);
    }

    @Bean
    public WebSocketService webSocketService() {
        TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
        strategy.setMaxSessionIdleTimeout(0L);
        return new HandshakeWebSocketService(strategy);
    }

}

and the Web socket handler class

@Component
public class ReactiveWebSocketHandler implements WebSocketHandler {

    private static final ObjectMapper json = new ObjectMapper();

    private Flux<String> eventFlux = Flux.generate(sink -> {
        Event event = new Event(randomUUID().toString(), now().toString());
        try {
            sink.next(json.writeValueAsString(event));
        } catch (JsonProcessingException e) {
            sink.error(e);
        }
    });

    private Flux<String> intervalFlux = Flux.interval(Duration.ofMillis(1000L))
      .zipWith(eventFlux, (time, event) -> event);

    @Override
    public Mono<Void> handle(WebSocketSession webSocketSession) {
        return webSocketSession.send(intervalFlux
          .map(webSocketSession::textMessage))
          .and(webSocketSession.receive()
            .map(WebSocketMessage::getPayloadAsText).log());
    }

}

var clientWebSocket = new WebSocket("ws://localhost:6600/ws/event-emitter");
clientWebSocket.onopen = function() {
  console.log("clientWebSocket.onopen", clientWebSocket);
  console.log("clientWebSocket.readyState", "websocketstatus");
  clientWebSocket.send("event-me-from-browser");
}
clientWebSocket.onclose = function(error) {
  console.log("clientWebSocket.onclose", clientWebSocket, error);
  events("Closing connection");
}
clientWebSocket.onerror = function(error) {
  console.log("clientWebSocket.onerror", clientWebSocket, error);
  events("An error occured");
}
clientWebSocket.onmessage = function(error) {
  console.log("clientWebSocket.onmessage", clientWebSocket, error);
  events(error.data);
}

function events(responseEvent) {
  document.querySelector(".events").innerHTML += responseEvent + "<br>";
}
like image 612
Atílio Araújo Avatar asked Feb 17 '26 07:02

Atílio Araújo


2 Answers

Spring WebFlux WebSocket:

Just adding this answer for people who are trying to achieve websocket with webflux.

  1. The OP has both web and webflux dependencies for some reason. However you just need WebFlux dependency only.
  2. Implement a WebSocketHandler
    @Service
    public class WebFluxWebSocketHandler implements WebSocketHandler {
    
        @Override
        public Mono<Void> handle(WebSocketSession webSocketSession) {
            Flux<WebSocketMessage> stringFlux = webSocketSession.receive()
                    .map(WebSocketMessage::getPayloadAsText)
                    .map(String::toUpperCase)
                    .map(webSocketSession::textMessage);
            return webSocketSession.send(stringFlux);
        }
    
    }
  1. Add a path - handler mapping. Here the handler is an instance of the websockethandler.
     @Bean
       public HandlerMapping handlerMapping(){
           Map<String, WebFluxWebSocketHandler> handlerMap = Map.of(
                   "/test", handler
           );
           return new SimpleUrlHandlerMapping(handlerMap, 1);
       }

That's it! Now the client should be able to connect to ws://localhost:8080/test

Check here for more info - https://www.vinsguru.com/spring-webflux-websocket/

like image 150
vins Avatar answered Feb 19 '26 19:02

vins


I could make it work removing spring-boot-starter-web and tomcat-embed-jasper dependencies. I suppose spring-boot-starter-web might conflict with spring-boot-starter-webflux.

Also I have changed my ReactiveWebSocketConfiguration class to set WebSocketService bean using ReactorNettyRequestUpgradeStrategy instead of TomcatRequestUpgradeStrategy:

@Bean
public WebSocketService webSocketService() {
    return new HandshakeWebSocketService(new ReactorNettyRequestUpgradeStrategy());
}

After those changes my jsp pages stopped working as my web container changed from Tomcat to Netty Web Server. I had to use Thymeleaf (added spring-boot-starter-thymeleaf dependency, configured its basic strucute and converted my pages to .html files) instead of traditional jsp.

I have opted to change from traditional jsp to Thymeleaf due to time constraints as my application has few and simple pages.

I have created a very simple project with all those configuration including a dummy page connecting to the websocket via javascript.

https://github.com/atilio-araujo/spring-webflux-websocket

like image 26
Atílio Araújo Avatar answered Feb 19 '26 19:02

Atílio Araújo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!