Spring WebFlux 和 WebSocket

Posted

技术标签:

【中文标题】Spring WebFlux 和 WebSocket【英文标题】:Spring WebFlux and WebSocket 【发布时间】:2020-03-15 11:27:43 【问题描述】:

我正在尝试将 WebSocket 功能添加到使用 Spring WebFlux 的现有应用程序中。它使用:

Spring Boot 2.2.1.RELEASE Tomcat 容器 配置为提供 jsp 页面

当我尝试通过 javascript(从 jsp 页面内部)连接到它时,我收到错误“失败:WebSocket 握手期间出错:意外响应代码:404

我注意到,如果我创建一个控制器类映射 url '/ws/event-emitter'(在我的 websocket 配置中映射的那个)接受 GET 请求,它会在尝试打开 websocket 连接时接收来自 Javascript 调用的请求,这让我更加困惑。

我错过了什么?

在网上看了一些教程后,我将我的配置设置如下:

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

https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.boot spring-boot-starter-parent 2.2.1.发布 br.com.samsung 监视器 0.0.1-快照 监视器 健康检查监视器

<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

公共类 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);

@组件 公共类 ReactiveWebSocketHandler 实现 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());

		<script>
		    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>";
		    
		</script>

【问题讨论】:

您是否尝试在 HandlerMapping 上设置 CORS 配置? 端口是否正确? @FelipeBonfante 是的,我已经尝试过 CORS 配置,但没有运气。无论如何,我的 Javascript 代码是从应用程序本身正在服务的 jsp 文件中运行的。 @ThomasAndolf 是的,他们是。我已经更改了默认的 8080 端口,因为我有另一个应用程序正在使用它。 【参考方案1】:

我可以让它工作删除 spring-boot-starter-webtomcat-embed-jasper 依赖项。我想 spring-boot-starter-web 可能与 spring-boot-starter-webflux 冲突。

我还更改了我的 ReactiveWebSocketConfiguration 类以使用 ReactorNettyRequestUpgradeStrategy 而不是 TomcatRequestUpgradeStrategy 设置 WebSocketService bean: p>

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

在这些更改之后,我的 jsp 页面停止工作,因为我的 Web 容器从 Tomcat 更改为 Netty Web Server。 我不得不使用 Thymeleaf(添加了 spring-boot-starter-thymeleaf 依赖项,配置了它的基本结构并将我的页面转换为 .html 文件)而不是传统的 jsp。

由于时间限制,我选择从传统的 jsp 更改为 Thymeleaf,因为我的应用程序页面很少且简单。

我创建了一个非常简单的项目,其中包含所有这些配置,包括一个通过 javascript 连接到 websocket 的虚拟页面。

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

【讨论】:

【参考方案2】:

Spring WebFlux WebSocket:

只需为试图通过webflux 实现websocket 的人添加此答案即可。

    出于某种原因,OP 同时具有 webwebflux 依赖项。但是,您只需要 WebFlux 依赖即可。 实现一个 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);
        
    
    
    添加路径 - 处理程序映射。这里的处理程序是 websockethandler 的一个实例。
     @Bean
       public HandlerMapping handlerMapping()
           Map<String, WebFluxWebSocketHandler> handlerMap = Map.of(
                   "/test", handler
           );
           return new SimpleUrlHandlerMapping(handlerMap, 1);
       

就是这样!现在客户端应该可以连接到ws://localhost:8080/test

点击此处了解更多信息 - https://www.vinsguru.com/spring-webflux-websocket/

【讨论】:

我想问一下我们可以通过ServerHttpRequest在你的例子中记录客户端信息吗?我们如何在 websocket webflux 中记录客户端信息?

以上是关于Spring WebFlux 和 WebSocket的主要内容,如果未能解决你的问题,请参考以下文章

深入剖析 Spring WebFlux

技术使用 Spring 5 的 WebFlux 开发反应式 Web 应用

spring webflux 和 webservice 同一个工程

spring webflux 和 webservice 同一个工程

@EnableResourceServer 不适用于 spring-webflux

Spring 5 WebFlux Mono 和 Flux