Agent Skills: GuicedEE WebSockets

RFC 6455 WebSocket support for GuicedEE using Vert.x 5: call-scoped connections, action-based message routing via IWebSocketMessageReceiver SPI, group management and broadcasting, WebSocketServerOptions, and lifecycle hooks. Use when adding WebSocket messaging, implementing real-time communication, managing WebSocket groups, or creating message receivers.

UncategorizedID: GuicedEE/ai-rules/guicedee-websockets

Install this agent skill to your local

pnpm dlx add-skill https://github.com/GuicedEE/ai-rules/tree/HEAD/skills/.system/guicedee-websockets

Skill Files

Browse the full folder contents for guicedee-websockets.

Download Skill

Loading file tree…

skills/.system/guicedee-websockets/SKILL.md

Skill Metadata

Name
guicedee-websockets
Description
"Implement GuicedEE WebSocket receivers, action routing, call-scoped connections, groups, and broadcasts."

GuicedEE WebSockets

Lightweight RFC 6455 WebSocket support for GuicedEE using Vert.x 5.

Core Concept

Connections are call-scoped, messages are dispatched through an action-based receiver SPI, and group membership is managed via the Vert.x EventBus. Builds on top of web for HTTP server plumbing.

Required Flow

  1. Add com.guicedee:websockets dependency (pulls in web transitively).
  2. Implement a message receiver:
    public class ChatReceiver implements IWebSocketMessageReceiver<Void, ChatReceiver> {
        @Override
        public Set<String> messageNames() {
            return Set.of("chat");
        }
    
        @Override
        public Uni<Void> receiveMessage(WebSocketMessageReceiver<?> message) {
            String text = (String) message.getData().get("text");
            IGuicedWebSocket ws = IGuiceContext.get(IGuicedWebSocket.class);
            ws.broadcastMessage("chat:lobby", text);
            return Uni.createFrom().voidItem();
        }
    }
    
  3. Register via JPMS:
    module my.app {
        requires com.guicedee.vertx.sockets;
        provides com.guicedee.client.services.websocket.IWebSocketMessageReceiver
            with my.app.ChatReceiver;
    }
    
  4. Bootstrap GuicedEE — WebSocket server starts automatically:
    IGuiceContext.registerModuleForScanning.add("my.app");
    IGuiceContext.instance().inject();
    

Message Protocol

Inbound messages are JSON with an action field for routing:

{ "action": "chat", "data": { "text": "Hello, world!" } }

| Field | Type | Required | Purpose | |---|---|---|---| | action | String | ✅ | Routes to matching IWebSocketMessageReceiver | | data | Map<String, Object> | ❌ | Arbitrary key/value payload | | broadcastGroup | String | ❌ | Auto-set to connection's RequestContextId | | webSocketSessionId | String | ❌ | Optional client-set session identifier |

Group Management

Every connection is added to the Everyone group automatically.

IGuicedWebSocket ws = IGuiceContext.get(IGuicedWebSocket.class);
ws.addToGroup("chat:lobby");
ws.removeFromGroup("chat:lobby");
ws.broadcastMessage("chat:lobby", "Hello everyone!");
ws.broadcastMessageSync("chat:lobby", "Immediate message");

SPI Lifecycle Hooks

| SPI | Purpose | |---|---| | GuicedWebSocketOnAddToGroup | Intercept group join | | GuicedWebSocketOnRemoveFromGroup | Intercept group leave | | GuicedWebSocketOnPublish | Intercept broadcast |

Connection Lifecycle

Client connects (ws://...)
 → CallScoper enters @CallScope
 → Connection added to "Everyone" group
 → Socket joins local membership; one EventBus consumer serves each group
 → textMessageHandler installed
   → JSON deserialized to WebSocketMessageReceiver
   → Action lookup → IWebSocketMessageReceiver.receiveMessage()
 → closeHandler removes from all groups

Cluster groups and bounded cleanup

Group broadcast publishes to the existing group event-bus address so other nodes receive it. Keep one consumer per group and idempotent socket membership; a fallback that loops only over the current JVM's sockets loses cluster fan-out. The current registry caps groups per node and recipients per group at 4096, and group names at 256 characters.

Close and group removal clear membership, unregister empty-group consumers, and remove call-scope/socket context properties. Some Vert.x server sockets have no textHandlerID; use a server-generated UUID and explicit socket/context mapping rather than a null map key. Cleanup must be idempotent across normal close, exceptions, and overload.

The current raw-socket path sets a 64 KiB write queue and rejects outgoing payloads above 1 MiB measured in UTF-8 bytes. A full queue closes with status 1013 using WebSocketBackpressure.close, including the two-second transport deadline. Route-upgraded sockets need capture at the HTTP request handoff. Prove physical closure and empty registries with an actual TCP peer that stops reading; a mocked writeQueueFull() result proves only branch handling.

RequestContextId, browser GUID, and client session fields are correlation data. Authenticate and authorize each protected action from verified server context. Use explicit Vert.x/server constructors only in isolated fixtures with deterministic teardown; production uses injected lifecycle resources.

Non-Negotiable Constraints

  • Module must requires com.guicedee.vertx.sockets;.
  • Message receiver packages must opens to com.google.guice.
  • DTO packages must opens to tools.jackson.databind.
  • SPI implementations must be dual-registered (module-info.java + META-INF/services/).
  • receiveMessage() returns Uni<Void> for non-blocking composition.