Spring Boot的web开发(3)

WebSocket
1 WebSocket是什么
WebSocket为浏览器和服务器提供双工异步通信(浏览器可以向服务器发送消息,服务器也可以向浏览器发送消息)。
WebSocket需要浏览器支持。
WebSocket是通过一个socket来实现双工异步通信能力的。
直接使用WebSocket过于麻烦,使用它的子协议STOMP,它是一个更高级别的协议,STOMP协议使用基于帧(frame)的格式来定义消息,与HTTP的request和response类似。
2 Spring Boot提供的自动配置
Spring Boot对内嵌的Tomcat(7、8)、Jetty9和Undertow使用WebSocket提供了支持。
配置源码存于org.springframework.boot.autoconfigure.websocket下.
Spring Boot为WebSocket提供的starter pom是spring-boot-starter-websocket.
3 实战
3.1 准备
新建Spring Boot项目,选择Thymeleaf和Websocket依赖
3.2广播式
广播式即服务端有消息时,会将消息发送给所有连接了当前endpoint的浏览器
(1)配置WebSocket,需要在配置类上使用@EnableWebSocketMessageBroker开启WebSocket支持,并通过集成AbstractWebSocketMessageBrokerConfigurer类,重写其方法来配置WebSocket.
package com.hand;

import org.springframework.context.annotation. Configuration ;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation. EnableWebSocketMessageBroker ;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

/**
* Created by lqy on 2017-11-28.
*/
@Configuration
@EnableWebSocketMessageBroker //通过注解开启使用STOMP协议来传输基于代理(message broker)的消息
//这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{

@Override
public void registerStompEndpoints(StompEndpointRegistry registry){
//注册STOMP协议的节点(endpoint),并映射到指定URL
registry.addEndpoint( "/endpointHand" ).withSockJS(); //注册一个STOMP的endpoint,并指定使用SockJS协议
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry){ //配置消息代理(Message Broker)
registry.enableSimpleBroker( "/topic" ); //广播式应配置一个/topic消息代理
}
}
(2)浏览器向服务端发送的消息用此类接受
package com.hand;

/**
* Created by lqy on 2017-11-28.
*/
public class HandMessage {
private String name ;
public String getName(){
return name ;
}
}
(3)服务端向浏览器发送的此类的消息
package com.hand;

/**
* Created by lqy on 2017-11-28.
*/
public class HandResponse {
private String responseMessage ;
public HandResponse(String responseMessage){
this . responseMessage =responseMessage;
}
public String getResponseMessage(){
return responseMessage ;
}
}
(4)演示控制器:
package com.hand;

import org.springframework.messaging.handler.annotation. MessageMapping ;
import org.springframework.messaging.handler.annotation. SendTo ;
import org.springframework.stereotype. Controller ;

/**
* Created by lqy on 2017-11-28.
*/
@Controller
public class HandController {
@MessageMapping ( "/welcome" ) //当浏览器向服务端发送请求时,通过该注解映射/welcome这个地址,类似@RequestMapping
@SendTo ( "/topic/getResponse" ) //当服务端有消息时,会对订阅了@Sendto中的路径的浏览器发送消息
public HandResponse say(HandMessage message) throws Exception{
Thread. sleep ( 3000 );
return new HandResponse( "welcome," +message.getName()+ "!" );
}
}
(5)添加脚本。
将stomp.min.js(STOMP协议的客户端脚本)、sockjs.min.js(SockJS的客户端脚本)以及jQuery放置在src/main/resources/static下。
(6)演示页面,在src/main/resources/templates下 新建hand.html
<!DOCTYPE html>
< html xmlns: th = "http://www.thymeleaf.org" >
< head >
< meta charset= "UTF-8" />
< title > Spring Boot+WebSocket+广播式 </ title >

</ head >
< body οnlοad= " disconnect () " >
< noscript >< h2 style= " color : #ff0000 " > 貌似你的浏览器不支持websocket </ h2 ></ noscript >
< div >
< div >
< button id= "connect" οnclick= " connect (); " > 连接 </ button >
< button id= "disconnect" disabled= "disabled" οnclick= " disconnect (); " > 断开连接 </ button >
</ div >
< div id= "conversationDiv" >
< label > 输入你的名字 </ label >< input type= "text" id= "name" />
< button id= "sendName" οnclick= " sendName (); " > 发送 </ button >
< p id= "response" ></ p >
</ div >
</ div >
< script th :src= "@{sockjs.min.js}" ></ script >
< script th :src= "@{stomp.min.js}" ></ script >
< script th :src= "@{jquery.js}" ></ script >
< script type= "text/javascript" >
var stompClient = null ;

function setConnected (connected) {
document . getElementById ( 'connect' ). disabled = connected;
document . getElementById ( 'disconnect' ). disabled = !connected;
document . getElementById ( 'conversationDiv' ). style . visibility = connected ? 'visible' : 'hidden' ;
$ ( '#response' ). html ();
}

function connect () {
var socket = new SockJS ( '/endpointHand' ); //连接SockJS的endpoint的名字为“/endpointHand”
stompClient = Stomp . over ( socket ); //使用STOMP子协议的WebSocker客户端
stompClient . connect ({}, function (frame) { //连接WebSocker服务端
setConnected ( true );
console . log ( 'Connected: ' + frame);
stompClient . subscribe ( '/topic/getResponse' , function (respnose){ //通过stompClient.subscribe订阅/topic/getResponse目标(destination)发送的消息,这个是在控制器的@Sento中定义的
showResponse ( JSON . parse (respnose. body ).responseMessage);
});
});
}


function disconnect () {
if ( stompClient != null ) {
stompClient . disconnect ();
}
setConnected ( false );
console . log ( "Disconnected" );
}

function sendName () {
var name = $ ( '#name' ). val ();
//通过stompClient.send向/welcome目标(destination)发送消息,这个是在控制器的MessageMapping中定义的
stompClient . send ( "/welcome" , {}, JSON . stringify ({ 'name' : name }));
}

function showResponse (message) {
var response = $ ( "#response" );
response . html (message);
}
</ script >
</ body >
</ html >

(7)配置viewController,为Hand.html提供便捷的路径映射
package com.hand;

import org.springframework.context.annotation. Configuration ;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
* Created by lqy on 2017-11-28.
*/
@Configuration
public class WebmvcConfig extends WebMvcConfigurerAdapter{
@Override
public void addViewControllers(ViewControllerRegistry registry){
registry.addViewController( "/hand" ).setViewName( "/Hand" );
}
}
(8)运行程序。
预期效果:一个浏览器发送一个消息到服务端时,其他浏览器也能接收到从服务端发送的这个消息。
开启三个浏览器,访问http://localhost:8080/hand,分别连接服务器,然后再浏览器发送一条消息,其他浏览器接收
连接服务端格式:
连接成功的返回:

订阅目标

向目标发送消息的格式

从目标接收的格式

3.3 点对点式
一对一聊天室:
需要用户相关的内容,引入最简单的Spring Security相关内容
(1)添加Spring Security的starter pom;
< dependency >
< groupId > org.springframework.boot </ groupId >
< artifactId > spring-boot-starter-security </ artifactId >
</ dependency >
(2)Spring Security的简单配置.
package com.hand;

import org.springframework.context.annotation. Configuration ;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration. EnableWebSecurity ;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
* Created by lqy on 2017-11-28.
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception{
http
.authorizeRequests()
.antMatchers( "/" , "/login" ).permitAll() //设置Spring Security对/和/"login"路径不拦截
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage( "/login" ) //设置Spring Security的登陆页面访问的路径为/login
.defaultSuccessUrl( "/chat" ) //登陆成功后转向/chat路径
.permitAll()
.and()
.logout()
.permitAll();
}
//在内存中分别配置两个用户lqy和lqy01,密码与用户名一致,角色是USER
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth
.inMemoryAuthentication()
.withUser( "lqy" ).password( "lqy" ).roles( "USER" )
.and()
.withUser( "lqy01" ).password( "lqy01" ).roles( "USER" );
}
///resources/static目录下的静态资源,Spring Security不拦截
@Override
public void configure(WebSecurity web) throws Exception{
web.ignoring().antMatchers( "/resources/static/**" );
}

}
(3)WebSocket;
package com.hand;

import org.springframework.context.annotation. Configuration ;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation. EnableWebSocketMessageBroker ;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

/**
* Created by lqy on 2017-11-28.
*/
@Configuration
@EnableWebSocketMessageBroker //通过注解开启使用STOMP协议来传输基于代理(message broker)的消息
//这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{

@Override
public void registerStompEndpoints(StompEndpointRegistry registry){
//注册STOMP协议的节点(endpoint),并映射到指定URL
//registry.addEndpoint("/endpointHand").withSockJS();//注册一个STOMP的endpoint,并指定使用SockJS协议
registry.addEndpoint( "/endpointChat" ).withSockJS(); //注册一个STOMP的endpoint,并指定使用SockJS协议
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry){ //配置消息代理(Message Broker)
//registry.enableSimpleBroker("/topic");//广播式应配置一个/topic消息代理
registry.enableSimpleBroker( "/queue" , "/topic" ); //点对点式新增一个/queue消息代理
}
}
(4)控制器
package com.hand;


import org.springframework.beans.factory.annotation. Autowired ;
import org.springframework.messaging.handler.annotation. MessageMapping ;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype. Controller ;

import java.security.Principal;

/**
* Created by lqy on 2017-11-28.
*/
@Controller
public class WebSecurityController {
@Autowired
private SimpMessagingTemplate messagingTemplate ; //通过SimpMessagingTemplate向浏览器发送消息
@MessageMapping ( "/chat" )
public void handleChat(Principal principal, String msg){ //在Spring MVC中,可以直接在参数中获取principal,principal中包含当前用户的信息
if (principal.getName().equals( "lqy" )){ //这是一段硬编码,如果发送人是Lqy,则发送给lqy01,反之。。
//通过messagingTemplate.convertAndSendToUser向用户发送消息,第一个参数是接收消息的用户,第二个是浏览器订阅的地址,第三个是消息本身
messagingTemplate .convertAndSendToUser( "lqy01" , "/queue/notifications" ,principal.getName()+ "-send:" +msg);
} else {
messagingTemplate .convertAndSendToUser( "lqy" , "/queue/notifications" ,principal.getName()+ "-send:" +msg);
}
}

}
(5)登录页面,src/main/resources/templates下新建login.html
<!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 >
(6)聊天页面
<!DOCTYPE html >

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

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

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

var sock = new SockJS ( "/endpointChat" ); //连接endpoint名为endpointChat的endpoint
var stomp = Stomp . over ( sock );
stomp . connect ( 'guest' , 'guest' , function (frame) {
stomp . subscribe ( "/user/queue/notifications" , handleNotification ); //订阅/user/queue/notifications发送的消息,这里与在控制器的messagingTemplate.convertAndSendToUser中定义的订阅地址保持一致。
//此处多一个/user,且是必须的,使用/user之后才能发送消息到指定用户
});



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 >
(7)增加页面的viewController;
package com.hand;

import org.springframework.context.annotation. Configuration ;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
* Created by lqy on 2017-11-28.
*/
@Configuration
public class WebmvcConfig extends WebMvcConfigurerAdapter{
@Override
public void addViewControllers(ViewControllerRegistry registry){
//registry.addViewController("/hand").setViewName("/Hand");
registry.addViewController( "/login" ).setViewName( "/login" );
registry.addViewController( "/chat" ).setViewName( "/chat" );
}
}
(8)运行
预期效果:两个用户登录系统,可以互发消息。一个浏览器用户会话session是共享的,在浏览器设置两个独立的用户,从而实现用户会话session隔离。http://localhos:8080/login

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值