Spring Boot实现STOMP协议的WebSocket
关注公众号:锅外的大佬
每日推送国外优秀的技术翻译文章,励志帮助国内的开发者更好地成长!
WebSocket
协议是应用程序处理实时消息的方法之一。最常见的替代方案是长轮询(long polling)和服务器推送事件(server-sent events)。这些解决方案中的每个都有其优缺点。在本文中,我将向您展示如何使用Spring Boot
实现WebSocket
。我将介绍服务器端和客户端设置,使用WebSocket
协议之上的STOMP
进行相互通信。
服务器端将完全用Java编码。但是,就客户端而言,我将展示用Java
和JavaScript(SockJS)
编写的片段,因为通常,WebSocket
客户端嵌入在前端应用程序中。代码示例将演示如何使用pub-sub
模型向多个用户广播消息以及如何仅向单个用户发送消息。在本文的另一部分中,我将简要讨论WebSocket安全问题以及如何确保即使环境不支持WebSocket
协议,基于WebSocket
的解决方案也能运行。
注意,WebSocket
安全话题仅在此处简要介绍,因为这是一个非常复杂的问题,可以单独撰写一篇文章。由于这个原因,以及我在文章最后一节WebSocket in production?
中提及的因素,我建议在生产中先对安全设置进行修改,直到生产就绪,安全措施到位为止。
1.WebSocket和STOMP协议
WebSocket
协议允许应用程序之间实现双向通信。重要的是要知道HTTP
仅用于初始握手。初次握手之后,HTTP
连接将升级为被WebSocket
使用的新TCP/IP
连接。
WebSocket
协议是一种相当低级的协议。它定义了如何将字节流转换为帧。帧可以包含文本或二进制消息。由于消息本身不提供有关如何路由或处理它的任何其他信息,因此很难在不编写其他代码的情况下实现更复杂的应用程序。幸运的是,WebSocket
规范允许在更高的应用程序级别上使用子协议。STOMP
是其中之一,由Spring Framework
支持。
STOMP
是一种简单的基于文本的消息传递协议,最初是为Ruby
,Python
和Perl
等脚本语言创建的,用于连接企业级消息代理。由于STOMP
,使不同语言开发的客户端和代理可以相互发送和接收消息。WebSocket
协议有时称为Web TCP
。以此类推,STOMP
被称为Web HTTP
。它定义了一些映射到WebSocket
帧的帧类型,例如CONNECT
,SUBSCRIBE
,UNSUBSCRIBE
,ACK
或SEND
。一方面,这些命令非常便于管理通信,另一方面,它们允许我们实现具有更复杂功能的解决方案,如消息确认。
2.服务端:Spring Boot和WebSocket
为了构建WebSocket
服务器端,我们将利用Spring Boot
框架,该框架使得在Java中开发独立程序和Web应用程序更快。 Spring Boot
包含spring-WebSocket
模块,该模块与Java WebSocket API
标准(JSR-356)兼容。
使用Spring Boot
实现WebSocket
服务器端并不是一项非常复杂的任务,只包含几个步骤,我们将逐一介绍。
*步骤1:*首先,添加WebSocket库依赖项。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
如果计划使用JSON
格式传输消息,则可能还需要包含GSON
或Jackson
依赖项。您还可能还需要一个安全框架,例如Spring Security
。
*步骤2:*然后,可以配置Spring
启用WebSocket
和STOMP
消息传递。
Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry
registry) {
registry.addEndpoint("/mywebsockets")
.setAllowedOrigins("mydomain.com").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config){
config.enableSimpleBroker("/topic/", "/queue/");
config.setApplicationDestinationPrefixes("/app");
}
}
configureMessageBroker
主要做两件事情:
- 创建内存中的消息代理,其中包含一个或多个用于发送和接收消息的目标。在上面的示例中,定义了两个目标地址前缀:
topic
和queue
。它们遵循以下惯例:通过pub-sub模型将以topic
为前缀的消息传递到所有订阅客户端的目标地址。另一方面,私有消息的目标地址通常以queue
为前缀。 - 定义前缀
app
,用于过滤目标地址,这些地址在Controller
中被@MessageMapping
修饰的方法处理。
image
服务器端如何处理消息
回到上面的代码段 - 可能你已经注意到对方法withSockJS()
的调用——它启用了SockJS
后备选项。简而言之,即使互联网浏览器不支持WebSocket
协议,它也会让我们的WebSockets
工作。我将进一步详细讨论这个主题。
还有一件事需要澄清——为什么我们在端点上调用setAllowedOrigins()
方法。一般是必需的,因为WebSocket
和SockJS
的默认行为是仅接受同源请求。因此,如果客户端和服务端处于不同的域,则需要调用此方法允许它们之间的通信。
*步骤3:*实现处理用户请求的控制器
它将向订阅特定主题的所有用户广播收到的消息。
这是一个将消息发送到目标地址/topic/news
的示例方法。
@MessageMapping("/news")
@SendTo("/topic/news")
public void broadcastNews(@Payload String message) {
return message;
}
也可以使用SimpMessagingTemplate
而不是注解@SendTo
,您可以在控制器内自动装配(Autowired)。
@MessageMapping("/news")
public void broadcastNews(@Payload String message) {
this.simpMessagingTemplate.convertAndSend("/topic/news", message)
}
在后面的步骤中,可能需要添加一些其他类来保护端点,例如Spring Security
框架中的ResourceServerConfigurerAdapter
或WebSecurityConfigurerAdapter
。此外,实现消息模型通常是有益的,这样传输的JSON
可以映射成对象。
3.WebSocket客户端构建
客户端实现是一项更简单的任务。
*步骤1:*装配Spring STOMP
客户端
@Autowired
private WebSocketStompClient stompClient;
步骤2: 打开连接
StompSessionHandler sessionHandler = new CustmStompSessionHandler();
StompSession stompSession = stompClient.connect(loggerServerQueueUrl,
sessionHandler).get();
此操作完成后,可以将消息发送到目的地。该消息将发送给所有订阅主题的用户。
stompSession.send("topic/greetings", "Hello new user");
以下方法也可以订阅消息
session.subscribe("topic/greetings", this);
@Override
public void handleFrame(StompHeaders headers, Object payload) {
Message msg = (Message) payload;
logger.info("Received : " + msg.getText()+ " from : " +
msg.getFrom());
}
有时需要向特定用户发送消息(例如,在实现聊天时)。然后,客户端和服务器端必须使用专用于此私人会话的单独目标地址。可以通过将唯一标识符附加到通用地址来创建目标地址的名称,例如/queue/chat-user123
。HTTP
会话或STOMP
会话标识符可用于此目的。
Spring
使发送私人消息变得更加容易。我们只需要使用@SendToUser
注释Controller
的方法。然后,目标地址将由UserDestinationMessageHandler
处理,它依赖于会话标识符。在客户端,当客户端订阅以/user
为前缀的目标地址时,此目标地址将转换为此用户唯一的目标地址。在服务器端,根据用户的Principal
解析用户目标地址。
服务器端使用@SendToUser
注解示例代码:
@MessageMapping("/greetings")
@SendToUser("/queue/greetings")
public String reply(@Payload String message,
Principal user) {
return "Hello " + message;
}
或者可以使用SimpMessagingTemplate
:
String username = ...
this.simpMessagingTemplate.convertAndSendToUser();
username, "/queue/greetings", "Hello " + username);
现在让我们看看如何实现一个能够接收私有消息的JavaScript(SockJS)
客户端,该客户端可以接收上面的示例中的Java代码发送的消息。值得一提的是,WebSockets
是HTML5
规范的一部分,并且受到大多数现代浏览器的支持(从版本10开始,Internet Explorer
支持它们)。
function connect() {
var socket = new SockJS('/greetings');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
stompClient.subscribe('/user/queue/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).name);
});
});
}
function sendName() {
stompClient.send("/app/greetings", {}, $("#name").val());
}
您可能已经注意到,要接收私人消息,客户端需要订阅前缀为/user
的目标地址/queue/greetings
。它不必担心任何唯一标识符。但是,在客户端登录应用程序之前,服务器端必须初始化Principal
对象。
4.WebSocket安全
许多Web
应用程序使用基于cookie
的身份验证,例如,我们可以使用Spring Security
限制已登录的用户访问某些页面或控制器限制。然后,通过基于cookie的HTTP会话维护用户上下文安全,该会话稍后与为该用户创建的WebSocket
或SockJS
会话相关联。 WebSocket
端点可以像任何其他请求一样受到保护,例如,在Spring
WebSecurityConfigurerAdapter
中的实现。
如今,Web
应用程序通常使用REST API
作为后端,使用OAuth/JWT
令牌进行用户身份验证和授权。 WebSocket
协议未描述服务器在HTTP
握手期间如何对客户端进行身份验证。实际上,标准HTTP
头(例如,授权)用于此目的。不幸的是,并非所有STOMP
客户端都支持它。 Spring
的STOMP
客户端允许为握手设置标头:
WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders();
handshakeHeaders.add(principalRequestHeader, principalRequestValue);
但是SockJS
的JavaScript客户端不支持使用SockJS
请求发送授权请求头(Authorization)。但是,它允许发送可用于传递令牌的查询参数。此方法需要在服务器端编写自定义代码,该代码将从查询参数中读取令牌并对其进行验证。特别重要的是确保令牌不与请求一起记录(或日志受到良好保护),因为这可能会导致严重的安全违规。
5.SockJS后备选项
与WebSocket
的集成可能并不总是尽如人意。某些浏览器(例如,IE 9)不支持WebSocket
。更重要的是,限制性代理可能使HTTP升级变得不可能,或者它们切断了打开太久的连接。在这种情况下,SockJS就会伸出援手。
SockJS
传输分为三大类:WebSocket
,HTTP Streaming
和HTTP Long Polling
。通信从SockJS
发送GET
/info
以从服务器获取基本信息开始。SockJS
根据响应决定使用的哪种传输方式。第一个选择是WebSocket
。如果不支持,则尽可能使用Streaming
。如果Streaming
也不可用,则选择轮询作为传输方法。
6.生产中使用WebSocket
虽然这种设置有效,但它并不是“最佳”。Spring Boot
允许您使用任何具有STOMP
协议的完整消息系统(例如,ActiveMQ,RabbitMQ),并且外部代理可以支持更多STOMP
操作(例如,确认,租借)而不是我们使用的简单代理。 STOMP Over WebSocket
提供有关WebSocket
和STOMP
协议的信息。它列出了处理STOMP
协议的消息传递系统,可能是在生产中使用的更好的解决方案。特别是由于请求数量很大,消息代理需要进行集群(Spring的简单消息代理不适合集群)。然后,不需要在WebSocketConfig
中启用简单代理,而是需要启用Stomp
代理中继,该中继将消息转发到外部消息代理和从外部消息代理转发消息。总而言之,外部消息代理可以帮助您构建更具伸缩性和可靠性的解决方案。
作者:Tomasz Dąbrowski
译者:Emma
Spring Boot实现STOMP协议的WebSocket的更多相关文章
- Spring Boot开发HTTPS协议的REST接口
Spring Boot开发HTTP的REST接口流程在前文中已经描述过,见<SpringBoot开发REST接口>. 如需要支持HTTPS,只需要在如上基础上进行设置.修改/resourc ...
- spring boot websocket stomp 实现广播通信和一对一通信聊天
一.前言 玩.net的时候,在asp.net下有一个叫 SignalR 的框架,可以在ASP .NET的Web项目中实现实时通信.刚接触java寻找相关替代品,发现 java 体系中有一套基于stom ...
- spring+rabbitmq+stomp搭建websocket消息推送(非spring boot方式)
前言: 两年前做过spring+activemq+stomp的ws推送,那个做起来很简单,但现在公司用的mq中间件是rabbitmq,因此需要通过rabbitmq去做ws通信.仔细搜了搜百度/谷歌,网 ...
- Spring Boot WebSocket从入门到放弃
在构建Spring boot项目时已经提供webSocket依赖的勾选.webSocket是TCP之上的一个非常薄的轻量级层 ,webSocket主要的应用场景离不开即时通讯与消息推送,但只要应用程序 ...
- springboot+websocket+sockjs进行消息推送【基于STOMP协议】
springboot+websocket+sockjs进行消息推送[基于STOMP协议] WebSocket是在HTML5基础上单个TCP连接上进行全双工通讯的协议,只要浏览器和服务器进行一次握手,就 ...
- 在spring boot中使用webSocket组件(一)
最近在项目中使用到了spring的webSocket组件,在这里和大家分享下,如有错误,欢迎大家指正. 在这里我使用的IDE工具是Intellij idea,框架是spring boot.spring ...
- Spring Boot -- 认识Spring Boot
在前面我们已经学习过Srping MVC框架,我们需要配置web.xml.spring mvc配置文件,tomcat,是不是感觉配置较为繁琐.那我们今天不妨来试试使用Spring Boot,Sprin ...
- Nginx 使用自签名证书实现 https 反代 Spring Boot 中碰到的页面跳转问题
问题一:页面自动跳转到 80 端口 问题描述 最近在使用Nginx反代一个Spring Boot项目中碰到了一个问题,使用 Spring Boot 中的 redirect: 进行页面跳转的时候,通过 ...
- 在Spring Boot框架下使用WebSocket实现消息推送
Spring Boot的学习持续进行中.前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目(使用Spring Boot开发Web项目)以及怎样为我们的Project添加HTTPS的 ...
随机推荐
- BZOJ4916 神犇和蒟蒻 【欧拉函数 + 杜教筛】
题目 很久很久以前,有一只神犇叫yzy; 很久很久之后,有一只蒟蒻叫lty; 输入格式 请你读入一个整数N;1<=N<=1E9,A.B模1E9+7; 输出格式 请你输出一个整数A=\sum ...
- LibreOJ2241 - 「CQOI2014」排序机械臂
Portal Description 给出一个\(n(n\leq10^5)\)个数的序列\(\{a_n\}\),对该序列进行\(n\)次操作.若在第\(i\)次操作前第\(i\)小的数在\(p_i\) ...
- showModalDialog实现本页面内部跳转
showModalDialog的弹窗中,要实现本窗口跳转而不打开新窗口,要么submit提交,要么按如下跳转,而不能采用location=xx来跳转:function go_link(url) { ...
- 关于oracle 11g导出的dmp文件无法导入10g的问题
今天遇到一个问题,由于无法远程11g是数据库服务器,只能用exp命令导出了一张表的dmp文件:在本地导入时遇到如下错误: IMP-00010: 不是有效的导出文件, 头部验证失败IMP-00000: ...
- vue.js源码学习分享(八)
/* */ var uid$1 = 0; /** * A dep is an observable that can have multiple * directives subscribing() ...
- Linux 虚拟内存和物理内存的理解【转】
转自:http://www.cnblogs.com/dyllove98/archive/2013/06/12/3132940.html 首先,让我们看下虚拟内存: 第一层理解 1. 每 ...
- Python struct 详解
最近在学习python网络编程这一块,在写简单的socket通信代码时,遇到了struct这个模块的使用,当时不太清楚这到底有和作用,后来查阅了相关资料大概了解了,在这里做一下简单的总结. 了解c语言 ...
- Day 19 函数之闭包、装饰器
一.什么是装饰器 器即函数 装饰即修饰,意指为其他函数添加新功能 装饰器定义:本质就是函数,功能是为其他函数添加新功能 二.装饰器遵循的原则 1.不修改被装饰函数的源代码(开放封闭原则) 2.为被装饰 ...
- Codeforces 321D Ciel and Flipboard(结论题+枚举)
题目链接 Ciel and Flipboard 题意 给出一个$n*n$的正方形,每个格子里有一个数,每次可以将一个大小为$x*x$的子正方形翻转 翻转的意义为该区域里的数都变成原来的相反数. ...
- 串口VMIN VTIME 详解
原文地址: 以前跟着做过VxWorks的开发,主要通信方式是串口,因为底层BSP包已经做好了,串口通信非常简单.后来接触Linux,在一块OK6410上跑Linux串口通信,才发现原来天真的以为甚是简 ...