Files
Nextus-Web/src/main/java/dev/coph/nextusweb/server/websocket/WebSocketConfig.java
T
CodingPhoenixx a7b65c031d
Auto Publish on Version Change / check-and-publish (push) Successful in 14s
Run Tests on Push and Pull Request / run-tests (push) Successful in 18s
Add constructors and Javadoc comments to improve clarity and completeness across server components, including WebSocket and routing classes.
2026-05-29 09:00:31 +02:00

297 lines
10 KiB
Java

package dev.coph.nextusweb.server.websocket;
import java.time.Duration;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Immutable configuration for the WebSocket subsystem: frame and message size limits, idle
* timeout, allowed origins, negotiated subprotocols, and compression. Instances are created
* through the nested {@link Builder}.
*
* <p>The values configured here govern how {@code HttpRequestHandler} sets up the WebSocket
* portion of the Netty pipeline during the upgrade handshake.</p>
*/
public final class WebSocketConfig {
/** Maximum size, in bytes, of a single WebSocket frame payload. */
private final int maxFramePayloadLength;
/** Maximum size, in bytes, of an aggregated (multi-frame) message. */
private final int maxAggregatedMessageSize;
/** Idle timeout after which an inactive connection is closed; {@code null} disables it. */
private final Duration idleTimeout;
/** Explicit set of allowed origins; ignored when {@link #allowAnyOrigin} is {@code true}. */
private final Set<String> allowedOrigins;
/** Whether connections from any origin are accepted. */
private final boolean allowAnyOrigin;
/** Subprotocols offered during negotiation. */
private final Set<String> subprotocols;
/** Whether per-message deflate compression is enabled. */
private final boolean compression;
/** Whether the protocol handler matches the path by prefix rather than exact equality. */
private final boolean checkStartsWith;
/**
* Builds an immutable configuration from a {@link Builder}, defensively copying its sets.
*
* @param b the builder carrying the configured values
*/
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;
}
/**
* Creates a configuration with all default values.
*
* @return a default configuration
*/
public static WebSocketConfig defaults() {
return builder().build();
}
/**
* Creates a new, empty {@link Builder}.
*
* @return a fresh builder
*/
public static Builder builder() {
return new Builder();
}
/**
* Tests whether a WebSocket upgrade from the given origin is permitted.
*
* @param origin the request's {@code Origin} header, may be {@code null}
* @return {@code true} if any origin is allowed, or if the origin is in the allow-list;
* {@code false} for a {@code null} or disallowed origin
*/
public boolean isOriginAllowed(String origin) {
if (allowAnyOrigin) return true;
if (origin == null) return false;
return allowedOrigins.contains(origin);
}
/**
* Returns the maximum size of a single WebSocket frame payload.
*
* @return the maximum single-frame payload size in bytes
*/
public int maxFramePayloadLength() {
return maxFramePayloadLength;
}
/**
* Returns the maximum size of an aggregated (multi-frame) message.
*
* @return the maximum aggregated message size in bytes
*/
public int maxAggregatedMessageSize() {
return maxAggregatedMessageSize;
}
/**
* Returns the idle timeout after which inactive connections are closed.
*
* @return the idle timeout, or {@code null} if idle connections are never closed
*/
public Duration idleTimeout() {
return idleTimeout;
}
/**
* Indicates whether connections from any origin are accepted.
*
* @return {@code true} if connections from any origin are accepted
*/
public boolean allowAnyOrigin() {
return allowAnyOrigin;
}
/**
* Returns the explicitly allowed origins.
*
* @return the immutable set of explicitly allowed origins
*/
public Set<String> allowedOrigins() {
return allowedOrigins;
}
/**
* Returns the configured subprotocols as a comma-separated string suitable for Netty's
* protocol config.
*
* @return the comma-separated subprotocol list, or {@code null} if none are configured
*/
public String subprotocolsCsv() {
if (subprotocols.isEmpty()) return null;
return String.join(",", subprotocols);
}
/**
* Indicates whether per-message deflate compression is enabled.
*
* @return {@code true} if per-message compression is enabled
*/
public boolean compression() {
return compression;
}
/**
* Indicates whether the WebSocket path is matched by prefix rather than exact equality.
*
* @return {@code true} if the WebSocket path is matched by prefix rather than exactly
*/
public boolean checkStartsWith() {
return checkStartsWith;
}
/**
* Fluent builder for {@link WebSocketConfig}, pre-populated with sensible defaults: 64&nbsp;KiB
* frames, 1&nbsp;MiB aggregated messages, a 60-second idle timeout, no origin restriction
* list, compression enabled, and exact path matching.
*/
public static final class Builder {
/** Maximum single-frame payload size in bytes; defaults to 64&nbsp;KiB. */
private int maxFramePayloadLength = 65_536;
/** Maximum aggregated message size in bytes; defaults to 1&nbsp;MiB. */
private int maxAggregatedMessageSize = 1_048_576;
/** Idle timeout; defaults to 60 seconds. */
private Duration idleTimeout = Duration.ofSeconds(60);
/** Accumulated allowed origins (insertion-ordered). */
private final Set<String> allowedOrigins = new LinkedHashSet<>();
/** Whether any origin is allowed; defaults to {@code false}. */
private boolean allowAnyOrigin = false;
/** Accumulated subprotocols (insertion-ordered). */
private final Set<String> subprotocols = new LinkedHashSet<>();
/** Whether compression is enabled; defaults to {@code true}. */
private boolean compression = true;
/** Whether path matching uses a prefix check; defaults to {@code false}. */
private boolean checkStartsWith = false;
/**
* Creates a builder pre-populated with the default configuration values described
* above. Obtain instances via {@link WebSocketConfig#builder()}.
*/
public Builder() {
}
/**
* Sets the maximum single-frame payload size.
*
* @param bytes the limit in bytes; must be positive
* @return this builder, for fluent chaining
* @throws IllegalArgumentException if {@code bytes <= 0}
*/
public Builder maxFramePayloadLength(int bytes) {
if (bytes <= 0) throw new IllegalArgumentException("maxFramePayloadLength must be > 0");
this.maxFramePayloadLength = bytes;
return this;
}
/**
* Sets the maximum aggregated message size.
*
* @param bytes the limit in bytes; must be positive
* @return this builder, for fluent chaining
* @throws IllegalArgumentException if {@code bytes <= 0}
*/
public Builder maxAggregatedMessageSize(int bytes) {
if (bytes <= 0) throw new IllegalArgumentException("maxAggregatedMessageSize must be > 0");
this.maxAggregatedMessageSize = bytes;
return this;
}
/**
* Sets the idle timeout after which inactive connections are closed.
*
* @param timeout the idle timeout
* @return this builder, for fluent chaining
*/
public Builder idleTimeout(Duration timeout) {
this.idleTimeout = timeout;
return this;
}
/**
* Disables the idle timeout, so connections are never closed for inactivity.
*
* @return this builder, for fluent chaining
*/
public Builder noIdleTimeout() {
this.idleTimeout = null;
return this;
}
/**
* Adds one or more origins to the allow-list.
*
* @param origins the origins to allow
* @return this builder, for fluent chaining
*/
public Builder allowedOrigins(String... origins) {
Collections.addAll(this.allowedOrigins, origins);
return this;
}
/**
* Allows WebSocket connections from any origin.
*
* @return this builder, for fluent chaining
*/
public Builder anyOrigin() {
this.allowAnyOrigin = true;
return this;
}
/**
* Adds one or more subprotocols to offer during negotiation.
*
* @param protocols the subprotocol names
* @return this builder, for fluent chaining
*/
public Builder subprotocols(String... protocols) {
Collections.addAll(this.subprotocols, protocols);
return this;
}
/**
* Enables or disables per-message compression.
*
* @param enabled {@code true} to enable compression
* @return this builder, for fluent chaining
*/
public Builder compression(boolean enabled) {
this.compression = enabled;
return this;
}
/**
* Sets whether the WebSocket path is matched by prefix rather than exact equality.
*
* @param v {@code true} to match by prefix
* @return this builder, for fluent chaining
*/
public Builder checkStartsWith(boolean v) {
this.checkStartsWith = v;
return this;
}
/**
* Builds the immutable {@link WebSocketConfig}.
*
* @return the configured instance
*/
public WebSocketConfig build() {
return new WebSocketConfig(this);
}
}
}