spring boot2 (32)-WebSocket和stomp消息

WebSocket可實現瀏覽器和服務器之間的通信,如在線聊天,消息推送等,其基於tcp協議來傳輸數據。而stomp是一種更高級的協議,可以更加方便的實現WebSocket。

broker和客戶端

客戶端可以是任何語言,如js,php等,只須使用stomp協議來收發消息,broker可對消息進行處理或轉發等。本篇將介紹以spring boot實現broker,以js實現客戶端。

pom.xml

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

配置消息端點

spring boot會自動配置好broker,我們只需添加端點如/endpoint1,客戶端通過端點建立連接。

@SpringBootConfiguration
@EnableWebSocketMessageBroker	//自動配置Broker
public class WebSocketConfig  implements WebSocketMessageBrokerConfigurer  {

    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/endpoint1").withSockJS();
    }

消息廣播

java代碼:每當請求/sent時向/topic1發送消息hello,world,所有訂閱了/topic1的客戶端都會收到這個消息。

    @Autowired
    private SimpMessagingTemplate messagingTemplate;
    
    @GetMapping("/sent")
    public void sent(HttpSession session) {
    	messagingTemplate.convertAndSend("/topic1", "hello,world"); 
    }
js代碼:向/endpoint1端點建立連接,同時訂閱/topic1的消息,並彈出窗口,顯示消息。中間有個{}傳參後面再講。
<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script type="text/javascript">
var socket = new SockJS('/endpoint1'); 
var stompClient = Stomp.over(socket);
stompClient.connect({}, function() {
    stompClient.subscribe('/topic1', function(respnose){
    	alert(respnose.body);
    });
});
啓動項目,首先打開js的頁面即會建立連接,每當服務器收到/sent請求便會向topic1發消息,頁面會自己彈出hello,world。

消息接收 

java代碼:當客戶端向/hello發消息時,在服務器控制檯打印消息

    @MessageMapping("/hello")
    public void hello(String message) {
    	    System.out.println(message);
html代碼:一個按鈕,用來觸發發消息事件
<button onclick="sendTest();">發消息</button>
js代碼:加到前面js代碼中,向/hello發消息,"test"會傳給後臺的message。這裏也有個{}和connect中的{}是一樣的作用,用來傳遞頭部參數,後面會講。
function sendTest() {
    stompClient.send("/hello", {}, "test");
}

權限攔截器

修改端點配置代碼,設置攔截器。每當客戶端連接端點時都會被攔截,在這裏可以讀取到session,進行驗證。和mvc攔截器類似,參考第19篇。當連接建立以後,通過該連接收發消息就不需要再次驗證了。

    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/endpoint1").withSockJS()
        .setInterceptors(new HandshakeInterceptor(){

			@Override
			public void afterHandshake(ServerHttpRequest arg0, ServerHttpResponse arg1, WebSocketHandler arg2,
					Exception arg3) {
			}

			@Override
			public boolean beforeHandshake(ServerHttpRequest arg0, ServerHttpResponse arg1, WebSocketHandler arg2,
					Map<String, Object> arg3) throws Exception {
				//拿到session,自己完成驗證代碼
				HttpSession session = getSession(arg0);
				return true;
			}
			
			private HttpSession getSession(ServerHttpRequest request) {
				if (request instanceof ServletServerHttpRequest) {
					ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request;
					return serverRequest.getServletRequest().getSession();
				}
				return null;
			}
        }); 請求
    }

請求攔截器

上面客戶端建立連接時,可以拿到session。而一但連接建立成功,就可以直接收發消息,但是無法拿到session或request。此時可以使用Principal來識別 每條消息的用戶來源。

在前面的WebSocketConfig類中加入以下方法,客戶端建立連接和收發消息時都會被它攔截,不影響上面的權限攔截器。這段代碼意思是:當用戶建立連接時,將前面說的{}中的參數username存入Principa。以後每次客戶端發消息時,都可以從Principa讀取到其username

   @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
    	//註冊攔截器
        registration.interceptors(new ChannelInterceptorAdapter() {
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                //判斷客戶端發出的命令是不是CONNECT
                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                	//這裏的username參數就是從前面的{}中傳過來的
                    Principal user =  new MyPrincipal(accessor.getNativeHeader("username").get(0));
                    //類似於設置session,在控制層獲取該參數時,會調用下面的MyPrincipal.getName(),下面會講
                    accessor.setUser(user);
                }
                //攔截處理完後轉發message,如果不允許該消息,可以返回null
                return message;
            }
        });
    }
    //內部類實現Principal接口
	class MyPrincipal implements Principal{
		private String name;
		public MyPrincipal(String name){
			this.name=name;
		}
		@Override
		public String getName() {
			return name;
		}
	}
修改/hello接收消息方法的參數,以下的principal.getName()就是上面的MyPrincipal.getName(),可獲取username參數值
    @MessageMapping("/hello")
	public void hello(Principal principal,String message) {
    	System.out.println(principal.getName());
修改js中的{}傳參如下,對應攔截器中的accessor.getNativeHeader("username")
stompClient.connect({username:"tom"}, function() {
此時,客戶端連接後,每次點擊按鈕向/hello發消息時,都會打印出其username,以此確認消息來源用戶。

點對點消息

點對點即向特定用戶發消息,上面已經確認消息來源,現在只需要確認特定的消息接收者。
修改hello方法,將tom發過來的message單獨發給jerry,而不再是公共廣播
    @MessageMapping("/hello")
	public void hello(Principal principal,String message) {
    	messagingTemplate.convertAndSendToUser("jerry","/topic1", principal.getName()+"say:"+message); 
修改js,注意固定語法/user/jerry,指只訂閱user用戶jerry的消息
	stompClient.subscribe('/user/jerry/topic1', function(respnose){
    	alert(respnose.body);
    });
此時,點擊消息按鈕時,將彈出來自tom的消息。這裏發消息和收消息都在一個頁面,出於方便我把username寫死了,實際可以動態設置用戶名,不同用戶互發消息,不解釋了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章