SpringBoot 配置基于 wss 和 STOMP 的 WebSocket

3,192 阅读3分钟

前言

上一篇文章中讲了如何在 SpringBoot 中配置 WebSocket 模拟实现群发消息的功能,本文则将进一步讲解如何在 SpringBoot 中配置基于 wss 协议和 STOMPWebSocket,本文假设你对 STOMP 协议有一定的了解,否则建议你先了解一下 STOMP 协议,可以参考这篇文章,此外不同于上一篇的代码示例比较复杂,本文将用尽可能少的代码为你展示效果,同样本文的完整代码已上传到GitHub,下面就正式开始。

效果展示

同上一篇一样,在展示具体的代码配置之前,先展示一下最终的效果:

demo1

下面是整个项目的目录结构:

├─main
│  ├─java
│  │  └─com
│  │      └─zjw
│  │          └─stomp
│  │              │  StompApplication.java
│  │              │
│  │              ├─config
│  │              │      TomcatConfiguration.java
│  │              │      WebSocketConfig.java
│  │              │
│  │              └─controller
│  │                      BroadcastController.java
│  │                      HTMLController.java
│  │
│  └─resources
│      │  application.yaml
│      │  keystore.jks
│      │
│      └─templates
│              greet.html
│              index.html
│
└─test
    └─java
        └─com
            └─zjw
                └─stomp
                        StompApplicationTests.java

Tips

想要生成以上目录树的结构,只需要在命令行使用 tree /f 文件夹名 即可,如果不想展示具体的文件,去掉 /f 参数即可。

具体配置

wss配置

想要设置 wss 协议只需要 SpringBoot 配置 https 即可,下面就讲解具体的步骤:

  1. 生成签名证书

    cmd 中输入以下命令,这里的 D:\develop\keystore.jks 即证书的生成路径,自己根据自己的情况修改即可,之后回车,根据提示输入自己的信息,即可在设置的路径下生成证书文件。

    keytool -genkeypair -alias tomcat -keyalg RSA -keystore D:\develop\keystore.jks
    
  2. 项目配置

    首先复制刚才生成的 keystore.jks 文件,然后粘贴到 resources 文件夹下,完成后,修改application.yaml(yml)文件:

    server:
      port: 443
      ssl.key-store: classpath:keystore.jks
      ssl.key-store-password: 123456
      ssl.key-password: 123456
      ssl.key-alias: tomcat
    

    如果是 properties 文件,改成下列形式即可:

    server.port=443
    server.ssl.key-store=classpath:keystore.jks
    server.ssl.key-store-password=123456
    server.ssl.key-password=123456
    server.ssl.key-alias=tomcat
    
  3. Tomcat 配置

    import org.apache.catalina.connector.Connector;
    import org.apache.tomcat.websocket.server.WsSci;
    import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
    import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
    import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class TomcatConfiguration {
    
        @Bean
        public ServletWebServerFactory servletContainer() {
            TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
            tomcat.addAdditionalTomcatConnectors(createSslConnector());
            return tomcat;
        }
    
        private Connector createSslConnector() {
            Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
            connector.setScheme("http");
            connector.setPort(8080);
            connector.setSecure(false);
            // 监听8080端口转发到443端口
            connector.setRedirectPort(443);
            return connector;
        }
    
        @Bean
        public TomcatContextCustomizer tomcatContextCustomizer() {
            return context -> context.addServletContainerInitializer(new WsSci(), null);
        }
        
    }
    
  4. 浏览器测试

    完成以上步骤后,在浏览器中的地址栏中输入 https://localhost/,只要能地址栏处显示不安全,就说明 https 配置成功:

    image-20210120175449445

STOMP配置

完成了 wss 也等于是 https 的配置后,就可以开始进行 STOMP 的配置了:

  1. 引入依赖

    首先需要引入以下必要的依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>webjars-locator</artifactId>
        <version>0.34</version>
    </dependency>
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>stomp-websocket</artifactId>
        <version>2.3.3</version>
    </dependency>
    
  2. 配置广播的 Controller

    import org.springframework.messaging.handler.annotation.MessageMapping;
    import org.springframework.messaging.handler.annotation.SendTo;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class BroadcastController {
    
        // 这里的 @MessageMapping 可以当成 @RequestMapping, 
        // 当有信息(broardcast 方法中的 msg 参数即为客服端发送的信息)发送到 /sendMsg 时,
        // broadcast 方法的返回的数据就会发送到所有订阅了 /broadcast/greet 的客户端
        // 关于如何订阅 /broadcast/greet 会在之后的客户端代码看到
        @MessageMapping("/sendMsg")
        @SendTo("/broadcast/greet")
        public String broadcast(String msg) {
            return msg;
        }
    
    }
    
  3. 配置 WebSocket 消息代理

    import org.springframework.context.annotation.Configuration;
    import org.springframework.messaging.simp.config.MessageBrokerRegistry;
    import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
    import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
    import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
    
    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry config) {
            // 开启一个简单的基于内存的消息代理
            // 将消息返回到订阅了带 /broadcast 前缀的目的客户端
            // 上述的 @SendTo 中的地址需要带有 /broadcast 前缀
            config.enableSimpleBroker("/broadcast");
        }
    
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            // 注册一个 /websocket 的 WebSocket 终端
            registry.addEndpoint("/websocket");
        }
    
    }
    
  4. 视图解析 Controller

    为了方便,项目在 templates 文件夹下建了 index.htmlgreet.html,这里需要配置一下视图解析:

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class HTMLController {
    
        @RequestMapping("/index")
        public String index() {
            return "index";
        }
    
        @RequestMapping("/greet")
        public String greet() {
            return "greet";
        }
    
    }
    
  5. 主页代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>主页</title>
        <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    </head>
    <body>
        <div id="greet"></div>
        <script>
    
            // 设置 WebSocket 的连接地址 wss://localhost/websocket
            let socket = new WebSocket('wss://localhost/websocket')
            let stompClient = Stomp.over(socket)
            stompClient.connect({}, function () {
                // 订阅到 /broadcast/greet, 即 @SendTo 内配置的地址
                stompClient.subscribe('/broadcast/greet', function (frame) {
                    // 获取消息帧的 body 内容, 显示到 <div id="greet"></div> 中
                    showGreeting(`收到信息: ${frame.body}`)
                })
            })
    
            function showGreeting(clientMessage) {
                document.getElementById("greet").innerText += `${clientMessage}\n`
            }
    
        </script>
    </body>
    </html>
    
  6. 发送消息界面的代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>群发信息</title>
        <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    </head>
    <body>
        <label><input type="text" id="msg"/></label>
        <button onclick="sendMsg()">发送</button>
        <script>
    
            // 设置 WebSocket 的连接地址 wss://localhost/websocket
            let socket = new WebSocket('wss://localhost/websocket')
            let stompClient = Stomp.over(socket);
    
            function sendMsg() {
                const msg = document.getElementById('msg').value
                // 发送输入框内的信息, /sendMsg 即为 @MessageMapping 中配置的地址
                stompClient.send("/sendMsg", {}, msg)
                alert('发送成功')
            }
    
        </script>
    </body>
    </html>
    

    完成以上配置后,即可实现效果展示中的效果。

总结

本文通过一个简单 demo,展示了如何使用集成了 wssSTOMPWebSocket,希望能够对你有所帮助,之后还会再通过一些更具体完整的示例代码,来讲解 WebSocket 的具体应用。