Introduction to WebSocket

WebSocket is a network protocol based on TCP. Born in 2009, in 2011 by IETF as the standard RFC 6455 communication standard, and by RFC7936 supplement specification. The WebSocket API is also a W3C standard.

WebSocket is also a full-duplex communication protocol that allows the client to proactively send messages to the server or the server to proactively send messages to the client. In WebSocket, the browser and server only need to complete a handshake, they can establish a persistent connection, two-way data transmission.

2. WebSocket features

  1. Used for connection handshake phaseHTTPAgreement;
  2. The protocol identifier isws, if encryption is usedwss;
  3. The data format is light, the performance overhead is small, and the communication is efficient.
  4. Clients can communicate with any server without the same origin restriction.
  5. Based on theTCPOn the protocol, the implementation of the server side is relatively easy;
  6. throughWebSocketYou can send text or binary data;
  7. withHTTPThe protocol has good compatibility. The default port is also80443And the handshake phase is usedHTTPProtocol, so handshake is not easy to shield, can pass through a variety ofHTTPProxy server;

Why WebSocket?

Before we talk about why we need WebSocket, we should first understand the time before WebSocket. At that time, the web-based messages basically relied on Http protocol for communication, and there were often “chat room”, “message push”, “real-time stock information dynamic” and so on. The following solutions are commonly used to achieve such requirements:

1. Short Polling (Traditional Polling)

Short polling means that the client asks the server every once in a while if there are any new messages and receives them if there are. This adds up to a lot of pointless requests, each time consuming traffic and processor resources.

Advantages: Short connection, simple server processing, cross-domain support, and good browser compatibility.

Disadvantages: certain latency, large server pressure, waste of bandwidth traffic, most invalid requests.

2. Long Polling

Long polling is an improvement of segment-based polling, in which the client performs an HTTP request and sends a message to the server, waiting for the server to respond, and if there is no new message, waiting until the server returns a new message or times out.

This is also a repetitive process, which only reduces the network bandwidth and processor consumption, but causes the problem of low real-time message and serious delay. Also based on loops, the fundamental bandwidth and processor resource usage is not effectively addressed.

Advantages: Reduced polling times, low latency, good browser compatibility.

Disadvantages: The server needs to maintain a large number of connections.

3. Server-sent Event

It is currently supported in all browsers except IE/Edge.

Server send events are a technique in which a server initiates a data transfer to a browser client. Once the initial connection is created, the event flow remains open until the client is closed. The technology is sent over traditional HTTP and has various features that WebSockets lack, such as “auto-reconnect,” “event ID,” and the ability to “send any event.”

Server sending events is a one-way channel that can only be sent from the server to the browser, because streaming information is essentially a download.

Advantages: It is suitable for frequent updates, low latency, and data is sent from the server to the client.

Disadvantages: Browser compatibility difficulty is high.

conclusion

Obviously, each of the above methods has its own advantages and disadvantages. Although polling can achieve some of these functions, its overhead and inefficiency can be fatal to performance, especially in mobile terminals nowadays.

Nowadays, there are more and more demands for two-way communication between client and server, and most browsers support WebSocket. Therefore, WebSocket is recommended for real-time and two-way communication and its efficiency.

4. WebSocket connection process

The first step

The client uses an HTTP request with the Upgrade:Websocket header to initiate a connection request to the server for a HandShake.

The HTTP request Header information is as follows:

Connection: Upgrade
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: IRQYhWINfX5Fh1zdocDl6Q==
Sec-WebSocket-Version: 13
Upgrade: websocket
Copy the code
  • Connection: Upgrade Indicates to Upgrade the protocol.
  • Upgrade: Websocket Updates the protocol to Websocket.
  • Sec-WebSocket-Extensions: represents extensions (such as message compression plug-ins) that the client wants to perform.
  • Sec-WebSocket-Key: used to verify the WebSocket protocol, corresponding to the server response headerSec-WebSocket-Accept.
  • Sec-WebSocket-VersionSaid:websocketVersion. If the server does not support this version, you need to return oneSec-WebSocket-VersionheaderWhich contains the version numbers supported by the server.

The second step

After the handshake is successful, the HTTP protocol is upgraded to the Websocket protocol to communicate with each other over a long connection.

The HTTP Header that the server responds to is as follows:

Connection: upgrade
Sec-Websocket-Accept: TSF8/KitM+yYRbXmjclgl7DwbHk=
Upgrade: websocket
Copy the code
  • Connection: Upgrade Indicates to Upgrade the protocol.
  • Upgrade: Websocket Updates the protocol to Websocket.
  • Sec-Websocket-Accept: the correspondingSec-WebSocket-KeyThe generated value is returned to the client for the client to verify the value to prove that the server supports itWebSocket.

5. WebSocket usage scenarios

  1. Data flow status: such as file upload and download, file progress, and whether the file is successfully uploaded.

  2. Collaborative document editing: The editing status of the same document must be synchronized to all participating user interfaces.

  3. Multiplayer games: Many games are co-operative, and player actions and states need to be synchronized to all players in a timely manner.

  4. Multiplayer chat: In many scenarios, multiple participants are required to participate in the discussion and chat. Messages sent by users must be synchronized to all users at the first time.

  5. Social subscriptions: Sometimes we need to receive timely subscriptions, such as lottery notifications, online invitations, payment results, etc.

  6. Stock virtual currency price: both stock and virtual currency prices fluctuate in real time and are closely related to users’ operations. Timely push is of great help for users to follow the stock market.

6. WebSocket neutron protocol support

WebSocket does specify a messaging architecture, but it does not enforce any particular messaging protocol. And it’s a very thin layer on TOP of TCP that converts byte streams into message flows (text or binary) and nothing more. It is up to the application to interpret the message.

Unlike HTTP, which is an application-level protocol, in the WebSocket protocol there is simply not enough information in the incoming message for the framework or container to know how to route or process it. Therefore, the level of the WebSocket protocol is too low for very trivial applications.

What you can do is bootstrap to create another layer of frames on top of it. This is equivalent to most Web applications today using Web frameworks rather than coding directly with Servlet apis.

WebSocket RFC defines the use of subprotocols. During the handshake, the client and server can agree on a subprotocol using the header SEC-WebSocket protocol, and even if a subprotocol is not needed but a higher application-level protocol, the application still needs to choose a message format that both the client and server can understand. And the format can be custom, framework-specific, or standard messaging protocols.

The Spring framework supports the use of STOMP, a simple messaging protocol originally created for a scripting language with a framework inspired by HTTP. STOMP is widely supported and is ideal for use on Websockets and the Web.

What is STOMP agreement

(1) Overview of STOMP Protocol

Simple Text-orientated Messaging Protocol (STOMP) is a Simple text-oriented Messaging Protocol.

It provides an interoperable connection format that allows STOMP clients to interact with any STOMP message Broker. STOMP is widely used in multiple languages and platforms due to its simple design and ease of client development.

(2) a brief introduction can be divided into the following points:

STOMP is a frame-based protocol whose frames are modeled on HTTP.

The STOMP framework consists of commands, an optional set of headers, and an optional principal.

STOMP is text-based, but also allows the transfer of binary messages.

The default encoding of STOMP is UTF-8, but it supports specifications for alternative encoding of message bodies.

(3). STOMP client is a user agent

As the producer, messages are sent to the target server via SEND frames.

As a consumer, you send a SUBSCRIBE frame to the target address and receive a MESSAGE from the server as a MESSAGE frame.

(4) STOMP frames

STOMP is a frame-based protocol whose frames are modeled on HTTP. The STOMP structure is:

COMMAND
header1:value1
header2:value2

Body^@
Copy the code

Clients can SEND or SUBSCRIBE messages using SEND or SUBSCRIBE commands, and they can also use the “destination” header to describe the content and recipient of the message.

This supports a simple publish-subscribe mechanism that can be used to send messages to other connected clients through a broker or to a server to request some work to be performed.

(5). Stomp common frames

Communication between STOMP’s client and server is achieved through “frames”. Each Frame consists of multiple “lines”. These frames are as follows:

  • Connecting Frames:
    • CONNECT (= CONNECT)
    • CONNECTED (successfully CONNECTED)
  • The Client Frames:
    • SEND (SEND)
    • SUBSRIBE (Subscription)
    • UNSUBSCRIBE
    • BEGIN
    • COMMIT
    • ABORT
    • ACK (confirm)
    • NACK (deny)
    • DISCONNECT)
  • Server Frames:
    • MESSAGE)
    • RECEIPT)
    • ERROR)

(6). Relationship between Stomp and WebSocket

Using WebSocket directly is very similar to writing a Web application using A TCP socket, because there is no high-level Wire Protocol, so you need to define the semantics of messages sent between applications and ensure that both ends of the connection follow those semantics.

Just as HTTP adds a request-response model layer on TOP of TCP sockets, STOMP provides a frame-based line format layer on top of WebSocket to define message semantics.

(7). Benefits of using STOMP as a WebSocket subprotocol

  1. No need to invent custom message formats
  2. Use the existing stomp.js client in your browser
  3. Ability to route messages to based on destination
  4. Mature message brokers can be used (for exampleRabbitMQ.ActiveMQEtc.) broadcast options
  5. useSTOMP(As opposed to ordinaryWebSocket) makeSpring FrameworkBeing able to provide a programming model for application-level use, as inSpring MVCOffer based onHTTPIs the same as the programming model.

Spring packaged STOMP

When using Spring’s STOMP support, the Spring WebSocket application acts as a STOMP proxy for the client.

Messages are routed to the @Controller message processing method or a simple in-memory agent that tracks subscriptions and broadcasts messages to subscribing users.

Spring can also be configured to work with dedicated STOMP agents (such as RabbitMQ, ActiveMQ, etc.) to actually broadcast messages. In that case, Spring maintains the TCP connection to the proxy, relays messages to the proxy, and passes messages down from the proxy to the connected WebSocket client.

So Spring Web applications can rely on unified HTTP-based security, common validation, and familiar programming model message handling.

Spring’s official processing flow diagram:

Some of the concepts in the above keywords:

  • Message: a Message with a header and payload.

  • MessageHandler: Entity that processes client messages.

  • MessageChannel: An entity that decouples a message sender from a message receiver

    • clientInboundChannel: Used to receive messages from WebSocket clients.
    • clientOutboundChannel: Used to send server messages to WebSocket clients.
    • brokerChannel: Used to send messages to a message broker from the server side, within an application
  • Broker: Middleware that holds messages. Clients can subscribe to messages from the Broker.

The above setup consists of three message channels:

  1. ClientInboundChannel: Used for messages from the WebSocket client.

  2. ClientOutboundChannel: Used to send messages to the WebSocket client.

  3. BrokerChannel: A message sent to the broker from within the application.

Example 1: Implement a simple broadcast mode

WebSocket is usually divided into broadcast mode and queue mode. In broadcast mode, information is sent to subscribers who subscribe to broadcast. As long as they subscribe to relevant broadcast, they can receive corresponding information.

The queue mode is commonly used in point-to-point mode to send messages from a single user to another user. The following describes an example of the broadcast mode.

1. Maven introduces dependencies

The Maven tool is used to manage dependency packages, introducing the following dependencies:

  1. Lombok: The Lombok tool relies on Get and Set methods for generating entity objects.

  2. Spring-boot-starter-websocket: SpringBoot implements webSocket dependencies, which encapsulate webSocket columns and include SpringBoot Web dependencies.

<dependencies>
        <! -- SpringBoot WebSocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <! -- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
</dependencies>
Copy the code

2. Create the test entity class

Create an entity class that is easy to transport messages with the following fields:

import lombok.Data;
@Data
public class MessageBody {
    /** Message content */
    private String content;
    /** The destination address for broadcast forwarding (telling STOMP agent where to forward it to) */
    private String destination;
}
Copy the code

3. Create a WebSocket configuration class

Create the WebSocket configuration class to configure the endpoint/MyDLQ for connection registration and the message broker prefix/Topic and the prefix /app for receiving clients to send messages.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /** * Configures the WebSocket entry point and enables the use of SockJS. These configurations are mainly used to configure connection endpoints for WebSocket connections@paramRegistry STOMP endpoint */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/mydlq").withSockJS();
    }

    /** * Configure the message broker option **@paramRegistry message broker registration configuration */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // Set one or more agent prefixes so that messages occurring in the methods of the Controller class are forwarded to the agent first to be sent to the corresponding broadcast or queue.
        registry.enableSimpleBroker("/topic");
        // Configure one or more prefixes for the client to send the request message, which filter the message target to be forwarded to the method corresponding to the annotation in the Controller class
        registry.setApplicationDestinationPrefixes("/app"); }}Copy the code

4. Create the test Controller class

Create a Controller class that looks like Controller in a normal Web project, annotating the method with @messagemapping. When the client sends a message request with a prefix that matches the /app prefix in the WebSocket configuration class, The match goes into the Controller class, and if the match is successful, the method content of the annotation is executed.

@Controller
public class MessageController {

    /** Message sending tool object */
    @Autowired
    private SimpMessageSendingOperations simpMessageSendingOperations;

    /** Broadcast sends a message to the specified destination address */
    @MessageMapping("/test")
    public void sendTopicMessage(MessageBody messageBody) {
        // The message is sent to the proxy configured in the WebSocket configuration class (/topic) for message forwardingsimpMessageSendingOperations.convertAndSend(messageBody.getDestination(), messageBody); }}Copy the code

5. Create a test script

Create JS file app-websocket.js to manipulate WebSocket:

// Set up the STOMP client
var stompClient = null;
// Set the WebSocket to enter the endpoint
var SOCKET_ENDPOINT = "/mydlq";
// Sets the request prefix for subscription messages
var SUBSCRIBE_PREFIX = "/topic"
// Set the request address for the subscription message
var SUBSCRIBE = "";
// Set the server endpoint, which interface in the server to access
var SEND_ENDPOINT = "/app/test";

/* Connect */
function connect() {
    / / set the SOCKET
    var socket = new SockJS(SOCKET_ENDPOINT);
    // Configure the STOMP client
    stompClient = Stomp.over(socket);
    // STOMP client connection
    stompClient.connect({}, function (frame) {
        alert("Connection successful");
    });
}

/* Subscription info */
function subscribeSocket(){
    // Set the subscription address
    SUBSCRIBE = SUBSCRIBE_PREFIX + $("#subscribe").val();
    // Print the subscription address
    alert("Set subscription address to:" + SUBSCRIBE);
    // Execute the subscription message
    stompClient.subscribe(SUBSCRIBE, function (responseBody) {
        var receiveMessage = JSON.parse(responseBody.body);
        $("#information").append("<tr><td>" + receiveMessage.content + "</td></tr>");
    });
}

/* Disconnect */
function disconnect() {
    stompClient.disconnect(function() {
        alert("Disconnect");
    });
}

/* Send the message and specify the destination address
function sendMessageNoParameter() {
    // Set what to send
    var sendContent = $("#content").val();
    // Set the message content to be sent
    var message = '{"destination": "' + SUBSCRIBE + '", "content": "' + sendContent + '"}';
    // Send a message
    stompClient.send(SEND_ENDPOINT, {}, message);
}
Copy the code

6. Create WebSocket HTML

Create a websocket-related WEB HTML page named index.html, with the following contents:

<! DOCTYPEhtml>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/sockjs-client/1.4.0/sockjs.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
    <script src="app-websocket.js"></script>
</head>
<body>
    <div id="main-content" class="container" style="margin-top: 10px;">
        <div class="row">
            <form class="navbar-form" style="margin-left:0px">
                <div class="col-md-12">
                    <div class="form-group">
                        <label>Connection:</label>
                        <button class="btn btn-primary" type="button" onclick="connect();">To connect</button>
                        <button class="btn btn-danger" type="button" onclick="disconnect();">disconnect</button>
                    </div>
                    <label>Subscription Address:</label>
                    <div class="form-group">
                        <input type="text" id="subscribe" class="form-control" placeholder="Subscription Address">
                    </div>
                    <button class="btn btn-warning" onclick="subscribeSocket();" type="button">To subscribe to</button>
                </div>
            </form>
        </div>
        </br>
        <div class="row">
            <div class="form-group">
                <label for="content">Contents of the sent message:</label>
                <input type="text" id="content" class="form-control" placeholder="Message content">
            </div>
            <button class="btn btn-info" onclick="sendMessageNoParameter();" type="button">send</button>
        </div>
        </br>
        <div class="row">
            <div class="col-md-12">
                <h5 class="page-header" style="font-weight:bold">Received message:</h5>
                <table class="table table-striped">
                    <tbody id="information"></tbody>
                </table>
            </div>
        </div>
    </div>
</body>
</html>
Copy the code

7. Start and test

Enter the address http://localhost:8080/index.html access to test the front page, and then perform the following steps to test:

  1. Click the Connect button to connect the WebSocket server;
  2. Enter the subscription address in the subscription address bar (because I set the subscription address and the address to receive the message is the same, so enter freely);
  3. Click the subscribe button to subscribe messages to the corresponding address;
  4. Enter it in the send message content input boxhello world!And then clicksendButton to send a message;

After completing the above steps, you can observe that the subscription address was successfully received as follows:

Example 2: Implementing the point-to-point pattern (introducing Spring Security for authentication)

1. Maven introduces dependencies

The Maven tool is used to manage dependency packages, introducing the following dependencies:

  1. lombok: Lombok tools rely on Get and Set methods for generating entity objects.
  2. spring-boot-starter-websocket: SpringBoot implements WebSocket dependencies, in which WebSocket is encapsulated in a number of columns, and also includes SpringBoot Web dependencies.
  3. spring-boot-starter-security: Spring Security, a Security framework based on Spring AOP and Servlet filters. It provides a comprehensive security solution that handles authentication and authorization at both the Web request level and method invocation level.
<dependencies>
        <! -- SpringBoot WebSocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <! -- Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <! -- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
Copy the code

2. Create the test entity class

Create an entity class that is easy to transport messages with the following fields:

@Data
public class MessageBody {
    /** The user who sent the message */
    private String from;
    /** Message content */
    private String content;
    /** Target user (telling STOMP proxy which user to forward to) */
    private String targetUser;
    /** The destination address for broadcast forwarding (telling STOMP agent where to forward it to) */
    private String destination;
}
Copy the code

3. Create a WebSocket configuration class

Create a WebSocket configuration class that configures the endpoint/MyDLQ for connection registration and the message broker prefix/Queue and the prefix /app for receiving clients to send messages.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /** * Configures the WebSocket entry point and enables the use of SockJS. These configurations are mainly used to configure connection endpoints for WebSocket connections@paramRegistry STOMP endpoint */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/mydlq").withSockJS();
    }

    /** * Configure the message broker option **@paramRegistry message broker registration configuration */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // Set one or more agent prefixes so that messages occurring in the methods of the Controller class are forwarded to the agent first to be sent to the corresponding broadcast or queue.
        registry.enableSimpleBroker("/queue");
        // Configure one or more prefixes for the client to send the request message, which filter the message target to be forwarded to the method corresponding to the annotation in the Controller class
        registry.setApplicationDestinationPrefixes("/app");
        // The server notifies a specific user of the prefix of the client. The default value is user
        registry.setUserDestinationPrefix("/user"); }}Copy the code

5. Create a Security configuration

Spring Security configuration class, where you can configure two user information about permission authentication and testing:

  1. Test user name/password 1: mydlQ1/123456
  2. Test username/password 2: mydlQ2/123456
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /** * Set the configuration parameters of the password encoding, set to NoOpPasswordEncoder, do not configure password encryption, convenient test. * *@returnPassword encoding instance */
    @Bean
    PasswordEncoder passwordEncoder(a) {
        return NoOpPasswordEncoder.getInstance();
    }

    /** * Sets the permission authentication parameters. This is used to create two user information for testing. * *@paramThe Auth SecurityBuilder is used to create the AuthenticationManager. *@throwsException Throws an Exception */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("mydlq1")
                .password("123456")
                .roles("admin")
                .and()
                .withUser("mydlq2")
                .password("123456")
                .roles("admin");
    }

    /** * Sets HTTP security-related configuration parameters **@paramHTTP HTTP Security object *@throwsException Throws an Exception */
    @Override
    protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .permitAll(); }}Copy the code

5. Create the test Controller class

Just like the broadcast mode described above, the function is also based on the WebSocket configuration class /app prefix match into the Controller class for logical processing operations.

@Controller
public class MessageController {

    @Autowired
    private SimpMessageSendingOperations simpMessageSendingOperations;

    /** * point to point to send a message to the specified user */
    @MessageMapping("/test")
    public void sendUserMessage(Principal principal, MessageBody messageBody) {
        // Set the user to send the message
        messageBody.setFrom(principal.getName());
        // Invoke STOMP proxy for message forwardingsimpMessageSendingOperations.convertAndSendToUser(messageBody.getTargetUser(), messageBody.getDestination(), messageBody); }}Copy the code

6. Create the WebSocket JS

Create JS file app-websocket.js to manipulate WebSocket:

// Set up the STOMP client
var stompClient = null;
// Set the WebSocket to enter the endpoint
var SOCKET_ENDPOINT = "/mydlq";
// Sets the request prefix for subscription messages
var SUBSCRIBE_PREFIX = "/topic"
// Set the request address for the subscription message
var SUBSCRIBE = "";
// Set the server endpoint, which interface in the server to access
var SEND_ENDPOINT = "/app/test";

/* Connect */
function connect() {
    / / set the SOCKET
    var socket = new SockJS(SOCKET_ENDPOINT);
    // Configure the STOMP client
    stompClient = Stomp.over(socket);
    // STOMP client connection
    stompClient.connect({}, function (frame) {
        alert("Connection successful");
    });
}

/* Subscription info */
function subscribeSocket(){
    // Set the subscription address
    SUBSCRIBE = SUBSCRIBE_PREFIX + $("#subscribe").val();
    // Print the subscription address
    alert("Set subscription address to:" + SUBSCRIBE);
    // Execute the subscription message
    stompClient.subscribe(SUBSCRIBE, function (responseBody) {
        var receiveMessage = JSON.parse(responseBody.body);
        $("#information").append("<tr><td>" + receiveMessage.content + "</td></tr>");
    });
}

/* Disconnect */
function disconnect() {
    stompClient.disconnect(function() {
        alert("Disconnect");
    });
}

/* Send the message and specify the destination address */
function sendMessageNoParameter() {
    // Set what to send
    var sendContent = $("#content").val();
    // Set the message content to be sent
    var message = '{"destination": "' + SUBSCRIBE + '", "content": "' + sendContent + '"}';
    // Send a message
    stompClient.send(SEND_ENDPOINT, {}, message);
}
Copy the code

7. Create WebSocket HTML

Create a websocket-related WEB HTML page named index.html, with the following contents:

<! DOCTYPEhtml>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/sockjs-client/1.4.0/sockjs.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
    <script src="app-websocket.js"></script>
</head>
<body>
    <div id="main-content" class="container" style="margin-top: 10px;">
        <div class="row">
            <form class="navbar-form" style="margin-left:0px">
                <div class="col-md-12">
                    <div class="form-group">
                        <label>Connection:</label>
                        <button class="btn btn-primary" type="button" onclick="connect();">To connect</button>
                        <button class="btn btn-danger" type="button" onclick="disconnect();">disconnect</button>
                    </div>
                    <label>Subscription Address:</label>
                    <div class="form-group">
                        <input type="text" id="subscribe" class="form-control" placeholder="Subscription Address">
                    </div>
                    <button class="btn btn-warning" onclick="subscribeSocket();" type="button">To subscribe to</button>
                </div>
            </form>
        </div>
        </br>
        <div class="row">
            <div class="form-group">
                <label for="content">Contents of the sent message:</label>
                <input type="text" id="content" class="form-control" placeholder="Message content">
            </div>
            <button class="btn btn-info" onclick="sendMessageNoParameter();" type="button">send</button>
        </div>
        </br>
        <div class="row">
            <div class="col-md-12">
                <h5 class="page-header" style="font-weight:bold">Received message:</h5>
                <table class="table table-striped">
                    <tbody id="information"></tbody>
                </table>
            </div>
        </div>
    </div>
</body>
</html>
Copy the code

8. Start and test

In order to facilitate the test, it is necessary to open two different types of browsers (because the Session will be saved after the user logs in, and the previous Session will be invalid if different users log in to the same browser) for the test. Two browsers also enter the address http://localhost:8080/index.html access to test the front page, and then can see no entry/index. The HTML page, Instead, jump to the /login page provided by Spring Security, as follows:

Enter username/password mydlQ1/123456 to log in with mydlQ2/123456 in both browsers, then return to /index.html, and perform the following steps to test:

  1. “Browser 1” and “browser 2” click the “Connect” button to connect the WebSocket server;

  2. Set the subscription address to “/ ABC” in both “Browser 1” and “Browser 2”, and click the subscribe button to subscribe messages.

  3. “Browser 1” (user mydlQ1) set the target user to “/ mydlQ2”, “browser 2” (user mydlQ2) set the target user to “/ mydlQ1”;

  4. “Browser 1” (user mydlQ1) set the message to Hi, I’m mydlQ1, “browser 2” (user mydlQ2) set the message to Hi, I’m mydlQ2;

  5. Click the Send button to send a message;

After completing the above steps, you can observe the following in two different browsers:

Example 3: Implementing point-to-point mode (authentication based on request Header)

1. Maven introduces dependencies

With the example 2

2. Create the test entity class

@Data
public class MessageBody {
    /** The user who sent the message */
    private String from;
    /** Message content */
    private String content;
    /** Target user (telling STOMP proxy which user to forward to) */
    private String targetUser;
    /** The destination address for broadcast forwarding (telling STOMP agent where to forward it to) */
    private String destination;
}

@Data
@AllArgsConstructor
public class User {
    private String username;
    private String token;
}
Copy the code

3. Configure the WebSocket channel interceptor

Configure WebSocket channel interceptor to add two simulated users:

  1. The usermydlq1.Token123456-1
  2. The usermydlq2.Token123456-2
/** * WebSocket channel interceptor (here simulates two test tokens for convenience testing, does not do the specific Token authentication implementation) **@author mydlq
 */
public class MyChannelInterceptor implements ChannelInterceptor {

    /** Test user with token 1 */
    private User mydlq1 = new User(""."123456-1");
    /** Test user with token 2 */
    private User mydlq2 = new User(""."123456-2");

    /** * Obtain the Token from the Header for authentication and differentiate users ** based on different tokens@paramMessage Message object *@paramChannel The channel object *@returnUser information after authentication */
    @Override
    publicMessage<? > preSend(Message<? > message, MessageChannel channel) { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); String token = getToken(message);if(token! =null&& accessor ! =null && StompCommand.CONNECT.equals(accessor.getCommand())) {
            Principal user = null;
            // Create two test tokens in advance for matching
            if (mydlq1.getToken().equals(token)){
                user = () -> mydlq1.getUsername();
            } else if (mydlq2.getToken().equals(token)){
                user = () -> mydlq2.getUsername();
            }
            accessor.setUser(user);
        }
        return message;
    }

    /** * Get TOKEN ** from Header@paramMessage Message object *@return TOKEN
     */
    private String getToken(Message
        message){
        Map<String,Object> headers = (Map<String, Object>) message.getHeaders().get("nativeHeaders");
        if(headers ! =null && headers.containsKey("token")){
            List<String> token = (List<String>)headers.get("token");
            return String.valueOf(token.get(0));
        }
        return null; }}Copy the code

4. Create a WebSocket configuration class

Create a WebSocket configuration class that configures the endpoint/MyDLQ for connection registration and the message broker prefix/Queue and the prefix /app for receiving clients to send messages.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /** * Configures the WebSocket entry point and enables the use of SockJS. These configurations are mainly used to configure connection endpoints for WebSocket connections@paramRegistry STOMP endpoint */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/mydlq").withSockJS();
    }

    /** * Configure the message broker option **@paramRegistry message broker registration configuration */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // Set one or more agent prefixes so that messages occurring in the methods of the Controller class are forwarded to the agent first to be sent to the corresponding broadcast or queue.
        registry.enableSimpleBroker("/queue");
        // Configure one or more prefixes for the client to send the request message, which filter the message target to be forwarded to the method corresponding to the annotation in the Controller class
        registry.setApplicationDestinationPrefixes("/app");
        // The server notifies a specific user of the prefix of the client. The default value is user
        registry.setUserDestinationPrefix("/user");
    }

    /** * Configure a channel interceptor to obtain the Header Token for authentication **@paramRegistration Channel configuration class */
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(newMyChannelInterceptor()); }}Copy the code

5. Create the test Controller class

@Controller
public class MessageController {

    @Autowired
    private SimpMessageSendingOperations simpMessageSendingOperations;

    /** * point to point to send a message to the specified user */
    @MessageMapping("/test")
    public void sendUserMessage(Principal principal, MessageBody messageBody) {
        // Set the user to send the message
        messageBody.setFrom(principal.getName());
        // Invoke STOMP proxy for message forwardingsimpMessageSendingOperations.convertAndSendToUser(messageBody.getTargetUser(), messageBody.getDestination(), messageBody); }}Copy the code

6. Create the WebSocket JS

Create JS file app-websocket.js to manipulate WebSocket:

// Set up the STOMP client
var stompClient = null;

// Set the WebSocket to enter the endpoint
var SOCKET_ENDPOINT = "/mydlq";
// Sets the request address prefix for subscription messages
var SUBSCRIBE_PREFIX  = "/queue";
// Set the subscription address
var SUBSCRIBE = "";
// Set the server endpoint, which interface in the server to access
var SEND_ENDPOINT = "/app/test";

/* Connect */
function connect() {
    / / set the SOCKET
    var socket = new SockJS(SOCKET_ENDPOINT);
    // Configure the STOMP client
    stompClient = Stomp.over(socket);
    / / access TOKEN
    var myToken = $("#myToken").val();
    // STOMP client connection
    stompClient.connect({token: myToken}, function (frame) {
        alert("Connection successful");
    });
}

/* Subscription info */
function subscribeSocket(){
    // Set the subscription address
    SUBSCRIBE = SUBSCRIBE_PREFIX + $("#subscribe").val();
    // Print the subscription address
    alert("Set subscription address to:" + SUBSCRIBE);
    // Execute the subscription message
    stompClient.subscribe("/user" + SUBSCRIBE, function (responseBody) {
        var receiveMessage = JSON.parse(responseBody.body);
        console.log(receiveMessage);
        $("#information").append("<tr><td>" + receiveMessage.content + "</td></tr>");
    });
}

/* Disconnect */
function disconnect() {
    stompClient.disconnect(function() {
        alert("Disconnect");
    });
}

/* Send the message and specify the destination address */
function sendMessageNoParameter() {
    // Set what to send
    var sendContent = $("#content").val();
    // Set the user to send
    var sendUser = $("#targetUser").val();
    // Set the message content to be sent
    var message = '{"targetUser":"' + sendUser + '", "destination": "' + SUBSCRIBE + '", "content": "' + sendContent + '"}';
    // Send a message
    stompClient.send(SEND_ENDPOINT, {}, message);
}
Copy the code

7. Create WebSocket HTML

Create a websocket-related WEB HTML page named index.html, with the following contents:

<! DOCTYPEhtml>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/sockjs-client/1.4.0/sockjs.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
    <script src="app-websocket.js"></script>
</head>
<body>
<div id="main-content" class="container" style="margin-top: 10px;">
    <div class="row">
        <form class="navbar-form" style="margin-left:0px">
            <div class="col-md-12">
                <div class="form-group">
                    <label>Connection:</label>
                    <button class="btn btn-primary" type="button" onclick="connect();">To connect</button>
                    <button class="btn btn-danger" type="button" onclick="disconnect();">disconnect</button>
                </div>
                <label>Subscription Address:</label>
                <div class="form-group">
                    <input type="text" id="subscribe" class="form-control" placeholder="Subscription Address">
                </div>
                <button class="btn btn-warning" onclick="subscribeSocket();" type="button">To subscribe to</button>
            </div>
        </form>
    </div>
    </br>
    <div class="row">
        <div class="form-group">
            <label>TOKEN information:</label>
            <input type="text" id="myToken" class="form-control" placeholder="TOKEN information">
            <label>To send to:</label>
            <input type="text" id="targetUser" class="form-control" placeholder="Sending user">
            <label for="content">Contents of the sent message:</label>
            <input type="text" id="content" class="form-control" placeholder="Content of the message">
        </div>
        <button class="btn btn-info" onclick="sendMessageNoParameter();" type="button">send</button>
    </div>
    </br>
    <div class="row">
        <div class="col-md-12">
            <h5 class="page-header" style="font-weight:bold">Received message:</h5>
            <table class="table table-striped">
                <tbody id="information"></tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>
Copy the code

8. Start and test

In order to facilitate the test, it is necessary to open two different types of browsers (here, user authentication is simulated by passing the Token through the Header. The specific login logic is not implemented, but two pre-configured user tokens are directly used for simulation). Two browsers also enter the address http://localhost:8080/index.html access to test the front page of ` ` / index HTML ` as follows:

  • Browser 1:
    • User: mydlq1
    • Token: 123456789-1
  • Browser 2:
    • Login user: mydlq2
    • Token: 123456789-2

Perform the following steps in both browsers to test:

  1. Browser 1 and Browser 2 click the connect button to connect to the WebSocket server.
  2. The browser 1andThe browser 2And set the subscription address to/abcAnd then click the Subscribe button to subscribe messages;
  3. Browser 1(user MyDLQ1) fills in the TOken string of simulated user MyDLQ1 in the TOken information column, and browser 2(user MyDLQ2) fills in the TOken string of simulated user MyDLQ2.
  4. The browser 1(user mydlQ1) Set the sending target user to/mydlq2.The browser 2(user mydlq2) set the sending target user to/mydlq1;
  5. Browser 1(user mydlQ1) sets the message to Hi, I’m mydlQ1, and browser 2(user mydlQ2) sets the message to Hi, I’m mydlQ2.
  6. Click the Send button to send a message;

After completing the above steps, you can observe the following in two different browsers:

12. SpringBoot and WebSocket common method examples

1. WebSocket enables the cross-domain option

WebSocket configuration class, which allows cross-domain Settings, as follows:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/queue");
        registry.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/mydlq")
        // Set "*" to allow all domain names
        .setAllowedOrigins("*") .withSockJS(); }}Copy the code

2. Listen on and offline WebSocket users

Create a WebSocket user online and offline processor as follows:

@Configuration
public class HttpWebSocketHandlerDecoratorFactory implements WebSocketHandlerDecoratorFactory {

    /** * Configures the webSocket handler **@paramWebSocketHandler webSocket processor *@returnWebSocket processor */
    @Override
    public WebSocketHandler decorate(WebSocketHandler webSocketHandler) {
        return new WebSocketHandlerDecorator(webSocketHandler) {
            /** * Action performed when webSocket connects *@paramSession WebSocket Session object *@throwsException Exception object */
            @Override
            public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
                // Displays information about websocket users
                if(session.getPrincipal() ! =null) {
                    String username = session.getPrincipal().getName();
                    System.out.println("Users." + username + "Online");
                    super.afterConnectionEstablished(session); }}/** * Action performed when webSocket closes the connection *@paramSession WebSocket Session object *@paramCloseStatus closeStatus object *@throwsException Exception object */
            @Override
            public void afterConnectionClosed(final WebSocketSession session, CloseStatus closeStatus) throws Exception {
                // Displays information about the user who closed the WebSocket connection
                if(session.getPrincipal() ! =null) {
                    String username = session.getPrincipal().getName();
                    System.out.println("Users." + username + "Offline");
                    super.afterConnectionClosed(session, closeStatus); }}}; }}Copy the code

Implement configureWebSocketTransport WebSocket configuration class () method, the above WebSocket processor to add to it, as follows:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/queue");
        registry.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/mydlq").withSockJS();
    }

    /** * Add WebSocket listener */
    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
        registry.addDecoratorFactory(newHttpWebSocketHandlerDecoratorFactory()); }}Copy the code

Xiii. Conclusion

This article introduces WebSocket from principle to practice in detail, hope you like…..

Finally don’t forget to click a like, ha ha………..

The article has been included at GitHub: github.com/JavaFamily

Source public number: boring learning Java

Author: Boring