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-web 和 tomcat-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 同时具有
web
和 webflux
依赖项。但是,您只需要 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 5 的 WebFlux 开发反应式 Web 应用
spring webflux 和 webservice 同一个工程
spring webflux 和 webservice 同一个工程