Extending WebSocket Transports |
![]() |
Net4j exposes a provider boundary for WebSocket implementations that need different client or server libraries. This article describes the supported factory and message-connection contracts and the obligations an implementation must satisfy. It deliberately does not present a fabricated third-party transport implementation.
Table of Contents
The built-in JDK provider is a public utility for configuring a registered factory. Give each distinct client policy
its own provider type and register the factory in the container that owns the connectors. The connector description's
provider: token selects that factory. This example uses the existing JDK provider with a custom opening
timeout; the application still owns the HTTP client.
Java 11 has no HttpClient.close(). On Java 21 and later, the application may close its client only after
every connector using that client has stopped; the provider factory does not own or close it.
A custom provider extends the SPI MessageConnectionFactory and implements
create(String description). It returns a fresh, not-yet-started IMessageConnection for each
connector. The factory may retain shared immutable configuration or a thread-safe client, but each returned connection
must have independent runtime state and be owned by its requesting connector.
Register providers under MessageConnectionFactory.PRODUCT_GROUP with a stable type string. In OSGi, the
bundle can contribute a factory through the standard Net4j factories extension point; programmatic registration is
useful when the provider depends on application-created configuration. The selected type must be present in the
connector's managed container before the provider connection is created.
The supported provider boundary is the message-connection SPI, not Net4j's internal JDK or Jetty implementation
classes. A client provider must perform its own handshake, emit complete Net4j frames, accept outgoing frames, and
report readiness, received data, writability, and closure to the message connection listener. A server provider must
perform upgrade routing and pass each accepted transport to IWSAcceptor.accept(IMessageConnection).
On the server, Jetty's adapter demonstrates one integration policy: it scopes a path mapping to an explicit set of
active acceptors and requires the path route and Net4jAcceptor header to agree. That policy is specific to
the Jetty adapter; do not treat its internal classes as extension APIs.
The public IWSConnector, IWSAcceptor, and IMessageConnection contracts are
appropriate integration points. Packages named internal are implementation details, even when visible in
a developer workspace or test bundle.
Providers must treat send buffers as borrowed storage. When send accepts a buffer, it returns
true and invokes exactly one terminal callback only after it stops accessing that storage. If it returns
false or throws before accepting the buffer, it must not retain the buffer or invoke the callback. It must
complete accepted sends during shutdown as well as normal operation.
Receive demand counts complete messages. Each delivery consumes one requested message, deliveries remain ordered, and each provided storage lease remains valid until its release action runs. The connector may request more data while an earlier frame is still leased, so a provider must retain each frame's storage independently. Abort must not wait indefinitely for callbacks.
Keep provider callbacks non-blocking where the underlying WebSocket API permits asynchronous completion. Do not add another unbounded queue that defeats Net4j's demand and buffer ownership contracts.
A provider factory belongs to its managed container; each connection belongs to one Net4j connector. Connector
activation starts its provider connection and connector deactivation closes or aborts it according to the transport
lifecycle. A factory must not close a shared HttpClient that belongs to the application.
Test factory registration and selection, independent state across two connections, failed handshakes, send acceptance and rejection, exactly-once completion, ordered receive demand, buffer release timing, graceful close, abort, and reactivation. The repository's JDK provider configuration and Jetty server adapter tests are useful references for scenario setup and lifecycle assertions. Run a test through the appropriate Net4j suite rather than relying only on direct construction when validating integration behavior.