Add WebSocket support with routing, origin validation, session management, and broadcasting
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package dev.coph.nextusweb.server.websocket;
|
||||
|
||||
import dev.coph.nextusweb.server.json.JsonMapper;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import tools.jackson.core.JacksonException;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class WebSocketSession {
|
||||
|
||||
static final AttributeKey<WebSocketSession> SESSION_KEY =
|
||||
AttributeKey.valueOf("nexusweb.ws.session");
|
||||
|
||||
private final Channel channel;
|
||||
private final String id;
|
||||
private final String path;
|
||||
private final Map<String, String> pathParams;
|
||||
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
|
||||
|
||||
WebSocketSession(Channel channel, String path, Map<String, String> pathParams) {
|
||||
this.channel = channel;
|
||||
this.id = UUID.randomUUID().toString();
|
||||
this.path = path;
|
||||
this.pathParams = pathParams;
|
||||
}
|
||||
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String pathParam(String name) {
|
||||
return pathParams.get(name);
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return channel.isActive();
|
||||
}
|
||||
|
||||
public String remoteAddress() {
|
||||
SocketAddress addr = channel.remoteAddress();
|
||||
if (addr instanceof InetSocketAddress inet) {
|
||||
return inet.getAddress().getHostAddress();
|
||||
}
|
||||
return addr == null ? null : addr.toString();
|
||||
}
|
||||
|
||||
public Channel channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public WebSocketSession attribute(String name, Object value) {
|
||||
if (value == null) attributes.remove(name);
|
||||
else attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T attribute(String name) {
|
||||
return (T) attributes.get(name);
|
||||
}
|
||||
|
||||
public ChannelFuture send(String text) {
|
||||
if (!channel.isActive()) return channel.newSucceededFuture();
|
||||
return channel.writeAndFlush(new TextWebSocketFrame(text));
|
||||
}
|
||||
|
||||
public ChannelFuture sendJson(Object value) {
|
||||
try {
|
||||
byte[] bytes = JsonMapper.MAPPER.writeValueAsBytes(value);
|
||||
if (!channel.isActive()) return channel.newSucceededFuture();
|
||||
ByteBuf buf = channel.alloc().buffer(bytes.length).writeBytes(bytes);
|
||||
return channel.writeAndFlush(new TextWebSocketFrame(true, 0, buf));
|
||||
} catch (JacksonException e) {
|
||||
throw new RuntimeException("JSON serialization failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public ChannelFuture sendBinary(byte[] data) {
|
||||
if (!channel.isActive()) return channel.newSucceededFuture();
|
||||
ByteBuf buf = channel.alloc().buffer(data.length).writeBytes(data);
|
||||
return channel.writeAndFlush(new BinaryWebSocketFrame(buf));
|
||||
}
|
||||
|
||||
public ChannelFuture ping() {
|
||||
if (!channel.isActive()) return channel.newSucceededFuture();
|
||||
return channel.writeAndFlush(new PingWebSocketFrame());
|
||||
}
|
||||
|
||||
public ChannelFuture close() {
|
||||
return close(1000, "");
|
||||
}
|
||||
|
||||
public ChannelFuture close(int code, String reason) {
|
||||
if (!channel.isActive()) return channel.newSucceededFuture();
|
||||
return channel.writeAndFlush(new CloseWebSocketFrame(code, reason))
|
||||
.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
static ChannelFuture sendRaw(Channel channel, String text) {
|
||||
if (!channel.isActive()) return channel.newSucceededFuture();
|
||||
ByteBuf buf = channel.alloc().buffer();
|
||||
buf.writeCharSequence(text, CharsetUtil.UTF_8);
|
||||
return channel.writeAndFlush(new TextWebSocketFrame(true, 0, buf));
|
||||
}
|
||||
|
||||
static ChannelFuture sendRawBinary(Channel channel, byte[] data) {
|
||||
if (!channel.isActive()) return channel.newSucceededFuture();
|
||||
ByteBuf buf = channel.alloc().buffer(data.length).writeBytes(Unpooled.wrappedBuffer(data));
|
||||
return channel.writeAndFlush(new BinaryWebSocketFrame(buf));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user