io.github.dengchen2020.websocket.BaseSpringWebSocketHandler Maven / Gradle / Ivy
The newest version!
package io.github.dengchen2020.websocket;
import jakarta.websocket.CloseReason;
import jakarta.websocket.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.*;
import org.springframework.web.socket.adapter.NativeWebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;
import org.springframework.web.socket.handler.SessionLimitExceededException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 基于spring封装的websocket的基础处理器,推荐使用
*
* @author dengchen
* @since 2024/6/26
*/
public class BaseSpringWebSocketHandler extends AbstractWebSocketHandler {
/**
* 处理来自基础 WebSocket的 文本消息
*
* @param session websocket会话
* @param message 文本消息
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
if (log.isDebugEnabled()) log.debug("收到文本消息:{}", message);
}
/**
* 处理来自基础 WebSocket的 二进制数据消息
*
* @param session websocket会话
* @param message 二进制数据消息
*/
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
if (log.isDebugEnabled()) log.debug("收到二进制数据消息:{}", message);
}
/**
* 处理来自基础 WebSocket的 pong消息
*
* @param session websocket会话
* @param message pong消息
*/
@Override
protected void handlePongMessage(WebSocketSession session, PongMessage message) {
if (log.isDebugEnabled()) log.debug("收到pong消息:{}", message.getPayload());
}
/**
* 在任一端关闭 WebSocket 连接后调用,或者在发生传输错误后调用。尽管从技术上讲,会话可能仍处于打开状态,但根据基础实现,不鼓励此时发送消息,并且很可能不会成功
*
* @param session websocket会话
* @param status 表示 WebSocket 关闭状态代码和原因。1xxx 范围内的状态代码由协议预定义。或者,可以发送带有原因的状态代码
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
getSessions().values().removeIf(s -> s.getId().equals(session.getId()));
close(session);
if (CLOSE_CODE.contains(status.getCode())) {
if (log.isDebugEnabled()) log.debug("连接关闭,原因是:{}", status.getReason());
} else {
log.warn("连接关闭,原因是:{}", status);
}
}
/**
* 处理来自基础 WebSocket 消息传输的错误
*
* @param session websocket会话
* @param exception 异常对象
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
if (log.isDebugEnabled()) log.debug("连接发生异常,原因是:{}", exception.toString());
}
private final Logger log = LoggerFactory.getLogger(getClass());
private static final Map extends DefaultWebSocketClientInfo, WebSocketSession> sessions = new ConcurrentHashMap<>();
private final Set CLOSE_CODE = Set.of(CloseReason.CloseCodes.NORMAL_CLOSURE.getCode(), CloseReason.CloseCodes.GOING_AWAY.getCode(), CloseReason.CloseCodes.CLOSED_ABNORMALLY.getCode(), CloseReason.CloseCodes.VIOLATED_POLICY.getCode());
@SuppressWarnings("unchecked")
public Map getSessions() {
return (Map) sessions;
}
/**
* 在 WebSocket 协商成功且 WebSocket 连接打开并可供使用后调用
*
* @param session websocket会话
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) {
if (session.getPrincipal() == null) {
CloseStatus status = CloseStatus.POLICY_VIOLATION.withReason("获取Token认证信息失败,请重新登录");
onlineFailEvent(session, status);
close(session, status);
return;
}
initSessionConfig(session);
getSessions().put(online(session), wrap(session));
onlineSuccessEvent(session);
}
/**
* 包装WebSocketSession,例如:
*
* new ConcurrentWebSocketSessionDecorator(session, 10 * 1000, 8 * 1024)
*
*
* @param session 原WebSocketSession
* @return 新WebSocketSession
*/
public WebSocketSession wrap(WebSocketSession session) {
return new ConcurrentWebSocketSessionDecorator(session,1000 * 10,1024 * 8);
}
/**
* 初始化会话配置
*
* @param session websocket会话
*/
public void initSessionConfig(WebSocketSession session) {
if(session instanceof NativeWebSocketSession nativeWebSocketSession){
Session nativeSession = nativeWebSocketSession.getNativeSession(Session.class);
if(nativeSession != null){
nativeSession.setMaxIdleTimeout(90 * 1000);
nativeSession.getAsyncRemote().setSendTimeout(10 * 1000);
}
}
session.setTextMessageSizeLimit(16 * 1024);
session.setBinaryMessageSizeLimit(1024 * 1024);
}
/**
* 上线
*
* @param session websocket会话
* @return websocket客户端信息
*/
public DefaultWebSocketClientInfo online(WebSocketSession session) {
return new DefaultWebSocketClientInfo(session.getId());
}
/**
* 获取客户端信息
*
* @param session websocket会话
* @return 客户端信息
*/
public DefaultWebSocketClientInfo getClientInfo(WebSocketSession session) {
return new DefaultWebSocketClientInfo(session.getId());
}
/**
* 上线成功事件
*
* @param session websocket会话
*/
public void onlineSuccessEvent(WebSocketSession session) {
if (log.isDebugEnabled()) log.debug("客户端{}上线成功,当前在线数量:{}", session.getId(), getSessions().size());
}
/**
* 上线失败事件
*
* @param session websocket会话
* @param status 异常原因状态代码
*/
public void onlineFailEvent(WebSocketSession session, CloseStatus status) {
if (log.isDebugEnabled()) log.debug("客户端{}上线失败", session.getId());
}
/**
* 关闭连接
*
* @param session websocket会话
*/
public void close(WebSocketSession session, CloseStatus closeStatus) {
try {
session.close(closeStatus);
} catch (IOException e) {
log.error("关闭连接失败,异常信息:{}", e.getMessage());
}
}
/**
* 关闭连接
*
* @param session websocket会话
*/
public void close(WebSocketSession session) {
try {
session.close();
} catch (IOException e) {
log.error("关闭连接失败,异常信息:{}", e.getMessage());
}
}
/**
* 发送ping消息
*
* @param session websocket会话
*/
public void sendPing(WebSocketSession session) {
try {
session.sendMessage(new PingMessage(ByteBuffer.allocate(1)));
} catch (SessionLimitExceededException e) {
close(session, e.getStatus());
} catch (Exception e) {
log.error("发送ping消息失败,异常信息:{}", e.getMessage());
}
}
/**
* 发送pong消息
*
* @param session websocket会话
*/
public void sendPong(WebSocketSession session) {
try {
session.sendMessage(new PongMessage(ByteBuffer.allocate(1)));
} catch (SessionLimitExceededException e) {
close(session, e.getStatus());
} catch (Exception e) {
log.error("发送pong消息失败,异常信息:{}", e.getMessage());
}
}
/**
* 向用户发送文本消息
*
* @param session websocket会话
* @param message 文本消息
*/
public void send(WebSocketSession session, String message) {
try {
session.sendMessage(new TextMessage(message));
} catch (SessionLimitExceededException e) {
close(session, e.getStatus());
} catch (Exception e) {
log.error("发送文本消息失败:{},异常信息:{}", message, e.getMessage());
}
}
/**
* 向用户发送二进制数据消息
*
* @param session websocket会话
* @param byteBuffer 二进制数据
*/
public void send(WebSocketSession session, ByteBuffer byteBuffer) {
try {
session.sendMessage(new BinaryMessage(byteBuffer));
} catch (SessionLimitExceededException e) {
close(session, e.getStatus());
} catch (Exception e) {
log.error("发送二进制数据消息失败:{},异常信息:{}", byteBuffer, e.getMessage());
}
}
}