Add WebSocket support with routing, origin validation, session management, and broadcasting

This commit is contained in:
CodingPhoenixx
2026-05-28 13:23:23 +02:00
parent b75e1e5c6e
commit 994e7fa80c
11 changed files with 799 additions and 4 deletions
@@ -0,0 +1,138 @@
package dev.coph.nextusweb.server.websocket;
import java.time.Duration;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
public final class WebSocketConfig {
private final int maxFramePayloadLength;
private final int maxAggregatedMessageSize;
private final Duration idleTimeout;
private final Set<String> allowedOrigins;
private final boolean allowAnyOrigin;
private final Set<String> subprotocols;
private final boolean compression;
private final boolean checkStartsWith;
private WebSocketConfig(Builder b) {
this.maxFramePayloadLength = b.maxFramePayloadLength;
this.maxAggregatedMessageSize = b.maxAggregatedMessageSize;
this.idleTimeout = b.idleTimeout;
this.allowedOrigins = Set.copyOf(b.allowedOrigins);
this.allowAnyOrigin = b.allowAnyOrigin;
this.subprotocols = Set.copyOf(b.subprotocols);
this.compression = b.compression;
this.checkStartsWith = b.checkStartsWith;
}
public static WebSocketConfig defaults() {
return builder().build();
}
public static Builder builder() {
return new Builder();
}
public boolean isOriginAllowed(String origin) {
if (allowAnyOrigin) return true;
if (origin == null) return false;
return allowedOrigins.contains(origin);
}
public int maxFramePayloadLength() {
return maxFramePayloadLength;
}
public int maxAggregatedMessageSize() {
return maxAggregatedMessageSize;
}
public Duration idleTimeout() {
return idleTimeout;
}
public boolean allowAnyOrigin() {
return allowAnyOrigin;
}
public Set<String> allowedOrigins() {
return allowedOrigins;
}
public String subprotocolsCsv() {
if (subprotocols.isEmpty()) return null;
return String.join(",", subprotocols);
}
public boolean compression() {
return compression;
}
public boolean checkStartsWith() {
return checkStartsWith;
}
public static final class Builder {
private int maxFramePayloadLength = 65_536;
private int maxAggregatedMessageSize = 1_048_576;
private Duration idleTimeout = Duration.ofSeconds(60);
private final Set<String> allowedOrigins = new LinkedHashSet<>();
private boolean allowAnyOrigin = false;
private final Set<String> subprotocols = new LinkedHashSet<>();
private boolean compression = true;
private boolean checkStartsWith = false;
public Builder maxFramePayloadLength(int bytes) {
if (bytes <= 0) throw new IllegalArgumentException("maxFramePayloadLength must be > 0");
this.maxFramePayloadLength = bytes;
return this;
}
public Builder maxAggregatedMessageSize(int bytes) {
if (bytes <= 0) throw new IllegalArgumentException("maxAggregatedMessageSize must be > 0");
this.maxAggregatedMessageSize = bytes;
return this;
}
public Builder idleTimeout(Duration timeout) {
this.idleTimeout = timeout;
return this;
}
public Builder noIdleTimeout() {
this.idleTimeout = null;
return this;
}
public Builder allowedOrigins(String... origins) {
Collections.addAll(this.allowedOrigins, origins);
return this;
}
public Builder anyOrigin() {
this.allowAnyOrigin = true;
return this;
}
public Builder subprotocols(String... protocols) {
Collections.addAll(this.subprotocols, protocols);
return this;
}
public Builder compression(boolean enabled) {
this.compression = enabled;
return this;
}
public Builder checkStartsWith(boolean v) {
this.checkStartsWith = v;
return this;
}
public WebSocketConfig build() {
return new WebSocketConfig(this);
}
}
}