8分钟阅读

使用Spring Boot进行WebSocket实现

Tomasz拥有100多年的Java应用程序经验,并为惠普等公司工作,以及硅谷初创公司。

网页Socket协议是使应用程序处理实时消息的方法之一。最常见的替代方案是长轮询和服务器发送的事件。这些解决方案中的每一个都具有其优点和缺点。在本文中,我将向您展示如何使用Spring Boot Framework实现WebSockets。我将介绍服务器端和客户端设置,我们将使用STOMP OVER WebSocket协议互相通信。

服务器端将纯粹在Java中编码。但是,在客户端的情况下,我将在Java和JavaScript(Sockjs)中显示两种代码段,因为通常,WebSockets客户端嵌入在前端应用程序中。代码示例将演示如何使用Pub-Sub模型向多个用户广播消息以及如何将仅发送给单个用户的消息。在文章的另一部分中,我将简要讨论保护WebSockets以及我们如何确保即使环境不支持WebSoction协议,也可以确保基于WebSocket的解决方案将保持运行。

请注意,只有在此处触摸安全性WebSocket的主题,因为它是一个单独的文章的复杂主题。由于这一点,以及我触摸的其他几个因素 网页Socket在生产中? 部分到底, 我建议在使用此设置之前进行修改 ,读取直到具有安全措施的生产准备设置的结束。

网页Socket和STOMP协议

网页Socket协议允许您在应用程序之间实现双向通信。重要的是要知道HTTP仅用于初始握手。发生后,HTTP连接升级到WebSocket使用的新打开的TCP / IP连接。

网页Socket协议是一个相当低级别的协议。它定义了字节流的转换为帧。帧可以包含文本或二进制消息。由于消息本身不提供有关如何路由或处理的任何其他信息,因此在不编写额外代码的情况下难以实现更复杂的应用程序。幸运的是,WebSocket规范允许使用更高的应用级别运行的子协议。其中一个由Spring Framework支持的是Stomp。

STOMP is a simple text-based messaging protocol that was initially created for scripting languages such as Ruby, Python, and Perl to connect to enterprise message brokers. Thanks to STOMP, clients and brokers developed in different languages can send and receive messages to and from each other. The WebSocket protocol is sometimes called TCP for Web. Analogically, STOMP is called HTTP for Web. It defines a handful of frame types that are mapped onto WebSockets frames, e.g., CONNECT, SUBSCRIBE, UNSUBSCRIBE, ACK, or SEND. On one hand, these commands are very handy to manage communication while, on the other, they allow us to implement solutions with more sophisticated features like message acknowledgment.

服务器端:Spring启动和WebSockets

To build the WebSocket server-side, we will utilize the Spring Boot framework which significantly speeds up the development of standalone and web applications in Java. Spring Boot includes the spring-WebSocket module, which is compatible with the Java WebSocket API standard (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("/话题/", "/queue/");
    config.setApplicationDestinationPrefixes("/应用程序");
  }
}

The method configureMessageBroker does two things:

  1. 创建内存 消息经纪人 with one or more destinations for sending and receiving messages. In the example above, there are two destination prefixes defined: topic and queue. They follow the convention that destinations for messages to be carried on to all subscribed clients via the pub-sub model should be prefixed with topic. On the other hand, destinations for private messages are typically prefixed by queue.
  2. Defines the prefix app that is used to filter destinations handled by methods annotated with @MessageMapping which you will implement in a controller. The controller, after processing the message, will send it to the broker.

春天 Boot WebSocket:如何在服务器端处理消息

如何在服务器端处理消息(源: 春天文档)


Going back to the snippet above—probably you have noticed a call to the method withSockJS()—it enables SockJS fallback options. To keep things short, it will let our WebSockets work even if the WebSocket protocol is not supported by an internet browser. I will discuss this topic in greater detail a bit further.

There is one more thing that needs clarifying—why we call setAllowedOrigins() method on the endpoint. It is often required because the default behavior of WebSocket and SockJS is to accept only same-origin requests. So, if your client and the server-side use different domains, this method needs to be called to allow the communication between them.

Step 3。实现将处理用户请求的控制器。它将将收到的消息播放给订阅给定主题的所有用户。

Here is a sample method that sends messages to the destination /话题/news.

@MessageMapping("/news")
@SendTo("/话题/news")
公共void广播新闻(@Payload String消息){
  return message;
}

Instead of the annotation @SendTo, you can also use SimpMessagingTemplate which you can autowire inside your controller.

@MessageMapping("/news")
公共void广播新闻(@Payload String消息){
  this.simpMessagingTemplate.convertAndSend("/话题/news", message)
}

In later steps, you may want to add some additional classes to secure your endpoints, like ResourceServerConfigurerAdapter or 网页SecurityConfigurerAdapter from the Spring Security framework. Also, it is often beneficial to implement the message model so that transmitted JSON can be mapped to objects.

构建WebSocket客户端

实现客户端是一个更简单的任务。

步骤1. Autowire Spring Stomp客户端。

@Autowired
private WebSocketStompClient stompClient;

步骤2.打开连接。

Speppsealdhandler会议手柄=新的custmstompsessionHandler();

StompSession stompSession = stompClient.connect(loggerServerQueueUrl, 
sessionHandler).get();

一旦完成了,就可以向目的地发送消息。该消息将被发送到订阅主题的所有用户。

stompSession.send("topic/greetings", "你好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());
}

Sometimes it is needed to send a message only to a dedicated user (for instance when implementing a chat). Then, the client and the server-side must use a separate destination dedicated to this private conversation. The name of the destination may be created by appending a unique identifier to a general destination name, e.g., /queue/chat-user123. HTTP Session or STOMP session identifiers can be utilized for this purpose.

春天 makes sending private messages a lot easier. We only need to annotate a Controller’s method with @SendToUser. Then, this destination will be handled by UserDestinationMessageHandler, which relies on a session identifier. On the client-side, when a client subscribes to a destination prefixed with /user, this destination is transformed into a destination unique for this user. On the server-side, a user destination is resolved based on a user’s Principal.

Sample server-side code with @SendToUser annotation:

@MessageMapping("/问候")
@SendToUser("/队列/问候")
公共字符串回复(@payload字符串消息,
   Principal user) {
 return  "你好"+消息;
}

或者你可以使用 SimpMessagingTemplate:

细绳 username = ...
this.simpMessagingTemplate.convertAndSendToUser();
   username, "/队列/问候", "你好" + username);

现在,我们看看如何实现能够接收私人消息的JavaScript(Sockjs)客户端,该私人消息可以在上面的示例中由Java代码发送。值得了解,WebSockets是HTML5规范的一部分,并且由大多数现代浏览器(Internet Explorer自版本10支持它们)支持。

function connect() {
 var socket = new sockjs('/greetings');
 stompClient = Stomp.over(socket);
 stompClient.connect({}, function (frame) {
   Stopplient.subscribe('/user/queue/greetings', function (greeting) {
     showGreeting(JSON.parse(greeting.body).name);
   });
 });
}

function sendName() {
 stompClient.send("/应用程序/greetings", {}, $("#name")。 val());
}

As you have probably noted, to receive private messages, the client needs to subscribe to a general destination /队列/问候 prefixed with /user. It does not have to bother with any unique identifiers. However, the client needs to login to the application before, so the Principal object on the server-side is initialized.

保护websockets.

Many web applications use cookie-based authentication. For instance, we can use Spring Security to restrict access to certain pages or Controllers only to logged users. User security context is then maintained through cookie-based HTTP session that is later associated with WebSocket or SockJS sessions created for that user. WebSockets endpoints can be secured as any other requests, e.g., in Spring’s 网页SecurityConfigurerAdapter.

如今,Web应用程序通常使用REST API作为其后端和OAuth / JWT令牌进行用户身份验证和授权。 WebSocket协议没有描述服务器在HTTP握手期间应该如何验证客户端。在实践中,标准的HTTP标题(例如,授权)用于此目的。遗憾的是,所有斯托门客户都不支持它。 Spring Java的Stomp客户端允许为握手设置标题:

网页SocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders();
handshakeHeaders.add(principalRequestHeader, principalRequestValue);

但是Sockjs JavaScript客户端不支持使用Sockjs请求发送授权标题。但是,它允许发送可用于传递令牌的查询参数。此方法需要在服务器端中编写自定义代码,这些代码将从查询参数中读取令牌并验证。同样重要的是要确保令牌不会与请求(或日志很好)一起登录,因为这可能会引入严重的安全违规行为。

Sockjs回归选项

与WebSocket集成可能并不总是顺利进行。一些浏览器(例如,IE 9)不支持WebSockets。更重要的是,限制性代理可能使得不可能执行HTTP升级或切割打开的连接太长。在这种情况下,Sockjs来到救援。

SockJS transports fall in three general categories: WebSockets, HTTP Streaming, and HTTP Long Polling. The communication starts with SockJS sending GET /info to obtain basic information from the server. Basing on the response, SockJS decides on the transport to be used. The first choice are WebSockets. If they are not supported, then, if possible, Streaming is used. If this option is also not possible, then Polling is chosen as a transport method.

网页Socket在生产中?

虽然这个设置有效,但它不是“最好的”。 Spring Boot允许您使用任何具有STOMP协议(例如ActiveMQ,RabbitMQ)的全方位消息传递系统,并且外部代理可以支持比我们使用的简单代理更具STOMP操作(例如,确认,收据)。 踩过WebSocket. provides interesting information about WebSockets and STOMP protocol. It lists messaging systems that handle STOMP protocol and could be a better solution to use in production. Especially if, due to the high number of requests, the message broker needs to be clustered. (Spring’s simple message broker is not suitable for clustering.) Then, instead of enabling the simple broker in 网页SocketConfig, it is required to enable the Stomp broker relay that forwards messages to and from an external message broker. To sum up, an external message broker may help you build a more scalable and robust solution.

如果你准备好继续你的 java.开发人员 旅程探索春靴,尝试阅读 使用Spring Boot for OAuth2和JWT REST保护 next.

理解基础知识

什么是stomp?

Stomp是简单(或流媒体)文本面向的消息传递协议。它使用一组命令,如连接,发送或订阅以管理对话。用任何语言编写的Stomp客户端可以与支持协议的任何消息代理交谈。

什么是websockets用于?

网页Sockets.通常用于使Web应用程序更具交互式。在实现社交饲料,在线聊天,新闻更新或基于位置的应用程序时,它们可能会有所帮助。

网页Socket如何工作?

网页Sockets.通过单个TCP连接提供双向通信通道。客户端通过称为WebSocket握手的过程建立持久连接。连接允许实时交换消息。

什么是春天启动以及它使用它的原因?

春天 Boot是一种基于Java的框架,可以更轻松地实现独立应用程序或微服务。它通常使用,因为它大大简化了与各种产品和框架的集成。它还包含一个嵌入式Web服务器,因此无需部署WAR文件。