spring boot websocket

1:WebSocket与 Http

WebSocket协议是2011年成为国际标准的、基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
HTTP1.1 允许只建立一次连接,多次资源请求复用该连接,也就是所谓的 keep-alive,但是 Websocket 其实是一个新协议,跟 HTTP 协议基本没有关系,
只是为了兼容现有浏览器,所以在握手阶段使用了 HTTP1.1。

首先我们来看个典型的 WebSocket 握手

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

Upgrade: websocket
Connection: Upgrade
这个就是 WebSocket 的核心了,告诉 Apache 、 Nginx 等服务器,发起的请求要用 WebSocket 协议。

Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
首先, Sec-WebSocket-Key 是一个 Base64 encode 的值,这个是浏览器随机生成的,告诉服务器:我要验证你是不是真的是 WebSocket 助理。

然后, Sec_WebSocket-Protocol 是一个用户定义的字符串,用来区分同URL下,不同的服务所需要的协议。简单理解:今晚我要服务A,别搞错啦~

最后, Sec-WebSocket-Version 是告诉服务器所使用的 WebSocket Draft (协议版本)。
然后服务器会返回下列东西,表示已经接受到请求, 成功建立 WebSocket 啦!

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
这里开始就是 HTTP 最后负责的区域了,告诉客户,我已经成功切换协议啦~

Upgrade: websocket
Connection: Upgrade
依然是固定的,告诉客户端即将升级的是 WebSocket 协议,而不是 mozillasocket,lurnarsocket 或者 shitsocket。

然后, Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key 。 服务器:好啦好啦,知道啦,给你看我的 ID CARD 来证明行了吧。

后面的, Sec-WebSocket-Protocol 则是表示最终使用的协议。

至此,HTTP 已经完成它所有工作了,接下来就是完全按照 WebSocket 协议进行了。

2.spring (lower)
SockJS 是 WebSocket 技术的一种模拟。为了应对许多浏览器不支持WebSocket协议的问题,设计了备选SockJs。
开启并使用SockJS后,它会优先选用Websocket协议作为传输协议,如果浏览器不支持Websocket协议,则会在其他方案中,
选择一个较好的协议进行通讯。
服务端使用:
registry.addEndpoint("/endpointChat").withSockJS();

客户端:
    <script th:src="@{sockjs.min.js}"></script>
    <script th:src="@{jquery-1.10.2.min.js}"></script>
    
    var ws = new SockJS('/marco');
    ws.onopen = function(evt) { 
        console.log("Connection open ..."); 
        ws.send("Hello WebSockets!");
    };

    ws.onmessage = function(evt) {
        console.log( "Received Message: " + evt.data);
        ws.close();
    };

    ws.onclose = function(evt) {
        console.log("Connection closed.");
    };      

服务端:

public class MarcoHandler extends AbstractWebSocketHandler {
    private static final Logger logger = LoggerFactory.getLogger(MarcoHandler.class);

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        super.handleMessage(session, message);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        Thread.sleep(2000);
        session.sendMessage(new TextMessage("Polo!"));
    }
}

配置 WebSocketConfigurer:
@Configuration
@EnableWebSocket
public class WebSocketConfigMarco implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        webSocketHandlerRegistry.addHandler(marcoHandler(), "/marco").withSockJS();
    }
    @Bean
    public MarcoHandler marcoHandler() {
        return new MarcoHandler();
    }
    
}

3. spring (STOMP)
STOMP,面向消息的简单文本协议。它是在WebSocket 之上提供一个基于帧的线路格式层,用来定义消息的语义。
websocket定义了两种传输信息类型: 文本信息和二进制信息。类型虽然被确定,但是他们的传输体是没有规定的。
所以,需要用一种简单的文本传输类型来规定传输内容,它可以作为通讯中的文本传输协议,即交互中的高级协议来定义交互信息。

STOMP本身可以支持流类型的网络传输协议: websocket协议和tcp协议。

Stomp还提供了一个stomp.js,用于浏览器客户端使用STOMP消息协议传输的js库。

STOMP的优点如下:

(1)不需要自建一套自定义的消息格式

(2)现有stomp.js客户端(浏览器中使用)可以直接使用

(3)能路由信息到指定消息地点

(4)可以直接使用成熟的STOMP代理进行广播 如:RabbitMQ, ActiveMQ

3.4 客户端:
<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{stomp.min.js}"></script>
<script th:src="@{jquery-1.10.2.min.js}" type="text/javascript"></script><!-- 2 -->

var stompClient = null;

    function connect() {
        var socket = new SockJS('/endpointWisely'); //1
        stompClient = Stomp.over(socket);
        stompClient.connect({}, function(frame) {
            stompClient.subscribe('/topic/getResponse', function(respnose){ //2
                showResponse(JSON.parse(respnose.body).responseMessage);
            });
        });
    }
    function showResponse(message) {
          var response = $("#response");
          response.html(message);
    }
   function disconnect() {
        if (stompClient != null) {
            stompClient.disconnect();
        }
    }

    function sendName() {
        var name = $('#name').val();
        stompClient.send("/welcome", {}, JSON.stringify({ 'name': name }));//3
    }

endpoint : /endpointWisely //1

url : /welcome  //2

subscribe: '/topic/getResponse' //3

3.1 启用 STOMP
@Configuration
@EnableWebSocketMessageBroker//1
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {}

@Configuration
@EnableWebSocketMessageBroker //1
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{

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

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic"); //3
    }

}

3.2 自定义匹配规则
//1 启动 STOMP 消息代理
//2 注册 STOMP 的endpoint  ,并指定使用 SocketJS协议
//3 配置 topic 代理

3.3 处理消息
@MessageMapping注解和@RequestMapping注解功能类似,只不过@RequestMapping表明此方法是Stomp客户端向服务端send消息的目标地址。
@Controller
public class WsController {

    @MessageMapping("/welcome")
    @SendTo("/topic/getResponse")
    public WiselyResponse say(WiselyMessage message) throws Exception {
        Thread.sleep(3000);
        return new WiselyResponse("Welcome, " + message.getName() + "!");
    }
}

4. 配置 Security 点对点

4.1 Security

4.1.1 添加Security jar 包

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

4.1.2 添加Security 配置

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/","login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/chat")
                .permitAll()
                .and()
                .logout()
                .permitAll();
    }
//4
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                .inMemoryAuthentication()
                .withUser("wyf").password("wyf").roles("USER")
                .and()
                .withUser("wisely").password("wisely").roles("USER");
    }
    //5忽略静态资源的拦截
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/static/**");
    }
}

4.2  WebSocket 

4.2.1 添加WebSocket 配置 

WebSocketConfig: registerStompEndpoints
registry.addEndpoint("/endpointChat").withSockJS();
WebSocketConfig:configureMessageBroker
registry.enableSimpleBroker("/queue","/topic");

4.2.2 添加控制器 WsController

    @Autowired
    private SimpMessagingTemplate messagingTemplate;//1

    @MessageMapping("/chat")
    public void handleChat(Principal principal, String msg) { //2
        if (principal.getName().equals("wyf")) {//3
            messagingTemplate.convertAndSendToUser("wisely",
                    "/queue/notifications", principal.getName() + "-send:"
                            + msg);
        } else {
            messagingTemplate.convertAndSendToUser("wyf",
                    "/queue/notifications", principal.getName() + "-send:"
                            + msg);
        }
    }

通过SimpMessagingTemplate 向浏览器发送信息 //1

在Spring MVC中,可以直接在参数中获取principal,principal中包含当前用户的信息//2

如果发送人是wyf,则发送给wisely;否则则相反//3

通过 convertAndSendToUser 向用户发送信息,第一个参数是接收消息用户,第二个是浏览器订阅地址,第三个是消息本身

4.3  客户端

4.3.1 添加登录页面

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<meta charset="UTF-8" />
<head>
    <title>登陆页面</title>
</head>
<body>
<div th:if="${param.error}">
    无效的账号和密码
</div>
<div th:if="${param.logout}">
    你已注销
</div>
<form th:action="@{/login}" method="post">
    <div><label> 账号 : <input type="text" name="username"/> </label></div>
    <div><label> 密码: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="登陆"/></div>
</form>
</body>
</html>

4.3.2 添加聊天页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8" />
<head>
    <title>Home</title>
    <script th:src="@{jquery-1.10.2.min.js}"></script>    <script th:src="@{sockjs.min.js}"></script>
    <script th:src="@{stomp.min.js}"></script>
</head>
<body>
<p>聊天室</p>

<form id="wiselyForm">
    <textarea rows="4" cols="60" name="text"></textarea>
    <input type="submit"/>
</form>

<script th:inline="javascript">
    $('#wiselyForm').submit(function(e){
        e.preventDefault();
        var text = $('#wiselyForm').find('textarea[name="text"]').val();
        sendSpittle(text);
    });

    var sock = new SockJS("/endpointChat"); //1
    var stomp = Stomp.over(sock);
    stomp.connect('guest', 'guest', function(frame) {
        stomp.subscribe("/user/queue/notifications", handleNotification);//2
    });

    function handleNotification(message) {
        $('#output').append("<b>Received: " + message.body + "</b><br/>")
    }

    function sendSpittle(text) {
        stomp.send("/chat", {}, text);//3
    }
    $('#stop').click(function() {sock.close()});
</script>
<div id="output"></div>
</body>
</html>

注1:连接 endpoint 名称 “/endpointChat”

    2: 订阅 /user/queue/notifications 发送的消息,这里与在控制器 messagingTemplate,convertAndSendToUser 中定义的订阅地址保持一致。这里的/user 是必须的,使用了/user 才会发送消息到指定的用户。

3:url ,/chat

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return s.equals(charSequence.toString());
    }
}

@Override // WebSecurityConfig
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
            .inMemoryAuthentication()
            .passwordEncoder(new MyPasswordEncoder())
            .withUser("wyf").password("wyf").roles("USER")
            .and()
            .withUser("wisely").password("wisely").roles("USER");
}

参考 : springboot 实战

spring 实战

https://www.cnblogs.com/nnngu/p/9347635.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值