Commits
Tarun Sukhani authored e25965e4baa
[PF-33] Netty 4 Stage C: WebSocket migration; retire deprecated Inbound ctors Final stage of the Netty 3 -> Netty 4 migration. Restores WebSocket support against the Netty 4 websocketx API, removes the per-connection static map in favour of channel attributes, and retires the ChannelHandlerContext-typed constructors on Http.Inbound and F.BlockingEventStream that were marked @Deprecated in PF-30 Phase 1. After this commit the framework has zero org.jboss.netty.* references repo-wide. Handshake (PlayHandler.websocketHandshake): - New WebSocketServerHandshakerFactory(url, subprotocols, allowExtensions, maxFramePayloadLength) — Netty 4 ctor takes max-frame-payload as a required arg. Reads play.netty.maxContentLength (default 65345) for the cap; clamps -1 to default since the Netty 4 ctor doesn't accept -1. - factory.newHandshaker(req) returns null on unsupported version, in which case sendUnsupportedVersionResponse closes the channel. - Route resolution moved BEFORE handshake completion: if Router has no WS route for the path, send 404 and close before upgrading. Avoids misleading the client with a successful upgrade to a dead endpoint. - handshaker.handshake(channel, request) is the synchronous entry point; it rewrites the pipeline (HTTP codec out, WebSocket frame codec in) before returning. Subsequent ctx.channelRead receives WebSocketFrame. - Inbound is constructed via the PlayChannel ctor (the ChannelHandlerContext ctor is gone — see below). - Outbound's send() and close() now use ctx.writeAndFlush instead of ctx.channel().write — Netty 4 separates write from flush, and the closure runs on arbitrary controller threads which Netty's Channel serializes to the EventLoop internally. Frame routing (PlayHandler.websocketFrameReceived): - CloseWebSocketFrame: ack via handshaker.close(channel, frame.retain()) (the handshaker writes the close response and closes the channel). - PingWebSocketFrame: respond with PongWebSocketFrame carrying frame.content().retain(). Refcount discipline: retain since the caller (channelRead.finally) will release the original frame. - PongWebSocketFrame: no-op (we don't currently track outbound pings). - BinaryWebSocketFrame: ByteBufUtil.getBytes(frame.content()) copies the payload to a heap byte[] (handles direct buffers correctly), then inbound._received(new Http.WebSocketFrame(bytes)). - TextWebSocketFrame: frame.text() reads the UTF-8 string lazily; handed to inbound._received(new Http.WebSocketFrame(string)). - ContinuationWebSocketFrame: logged and dropped — Play's existing contract delivers complete messages, not fragments. Multi-frame reassembly is a separate enhancement (most simple text/binary payloads fit in a single frame). - All frame.release() calls happen in PlayHandler.channelRead's existing finally clause via ReferenceCountUtil.release(msg). Per-channel state moved from static Map to AttributeKey: - WS_INBOUND: the active Http.Inbound for this connection - WS_HANDSHAKER: the handshaker (needed for close-frame ack) The previous static Map<ChannelHandlerContext, Http.Inbound> was a Netty 3-era pattern that did not benefit from per-channel locality and made GC of disconnected sessions awkward. AttributeKey is the canonical Netty 4 idiom; channel attributes are cleared automatically when the channel is closed. Lifecycle (PlayHandler.channelInactive): - Reads + clears the WS_INBOUND attr atomically (getAndSet null) and calls inbound.close() so any blocked nextEvent() promises resolve with a WebSocketClose event. WS_HANDSHAKER is also cleared. WebSocketInvocation inner class restored without the Netty 3 MessageEvent field. Otherwise unchanged: same init() ThreadLocal wiring, same WebSocketInvoker.resolve/invoke calls, same onException/onSuccess semantics. ctx.channel() (Netty 4) replaces ctx.getChannel() (Netty 3). Public API break: deprecated ctors retired - Http.Inbound(ChannelHandlerContext) — gone. Apps subclassing Http.Inbound must now call super(playChannel) instead of super(ctx). NettyPlayChannel is still public; users can wrap a ChannelHandlerContext if they need to bridge from Netty-aware code, but most subclasses can take a PlayChannel directly. - F.BlockingEventStream(ChannelHandlerContext) and F.BlockingEventStream(int, ChannelHandlerContext) — gone. Same migration path: use the PlayChannel-typed ctors. - Http.java and F.java no longer import ChannelHandlerContext or NettyPlayChannel; F.java in particular now has zero io.netty.* imports — the WebSocket transport layer no longer leaks into the generic Promise/EventStream library. Verified with `ant clean unittest`: 295 tests pass, 0 failures. `git grep org.jboss.netty -- '*.java' '*.conf' '*.yml'` returns zero matches across all tracked files. PF-30 acceptance closes with this commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>