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寫死了,實際可以動態設置用戶名,不同用戶互發消息,不解釋了。