Class WebSocketSession

java.lang.Object
dev.relism.flash.websocket.WebSocketSession

public final class WebSocketSession extends Object
Per-connection WebSocket I/O state. One instance per virtual thread.
  • With TCP_NODELAY enabled on the socket (set by the connection runner), Nagle's algorithm is disabled: the kernel sends data as soon as it lands in the send buffer, without waiting. BufferedOutputStream will still batch multiple small writes into one syscall when they happen in the same virtual-thread scheduling quantum — giving us coalescing where it's free, and immediate delivery where it matters.

    The only place an explicit flush is still needed is after the WS handshake (one-time, not on the hot path) and after the CLOSE frame (end of session). Both are handled in close(int) and in WebSocketUpgrade#performHandshake.

  • Flush on CLOSE frame: close(int) still flushes explicitly because the CLOSE frame is the last thing written before the stream is abandoned — without a flush, the 4 bytes could sit in the buffer forever.

Thread safety

sendText(byte[], int, int), send(byte[], int, int), and close(int) are serialized on a blocking inside synchronized pins its carrier platform thread on Java 21, and a blocking socket write is exactly the kind of call that can block. ReentrantLock unmounts the blocked virtual thread instead) and are safe to call from threads other than the session loop. close(int) uses a CAS on open to guarantee exactly-once CLOSE frame emission under concurrent calls.

Fragmentation, masking, and control frames (RFC 6455 §5)

readFrame(dev.relism.flash.websocket.WebSocketFrame) reassembles continuation frames into one logical message (bounded by the read buffer's capacity — the same bound a single unfragmented frame already had), enforces that incoming frames are masked exactly when this session's role requires it (server sessions require masked frames from the client; client-mode sessions require unmasked frames from the server), validates the opcode against RFC 6455's defined set, and enforces the control-frame constraints (FIN must be set, payload ≤ 125 bytes). A violation throws WebSocketProtocolException carrying the correct close code (1002 protocol error, 1009 message too big) for the caller to send before closing.
  • Constructor Details

    • WebSocketSession

      public WebSocketSession(InputStream in, OutputStream out, int bufferSize)
    • WebSocketSession

      public WebSocketSession(InputStream in, OutputStream out, int bufferSize, Request request, boolean maskOutgoing)
      Parameters:
      request - the HTTP request that upgraded to this session, or null if the caller has no use for it (e.g. a session opened as a WS client rather than accepted as a WS server). Stored as-is, no copy.
      maskOutgoing - true if this session is acting as a WS client — RFC 6455 requires client-to-server frames to be masked, unlike the server-to-client direction writeFrame(byte, byte[], int, int) originally only supported. See writeFrame(byte, byte[], int, int) for how masking is applied without allocating. Also determines the expected masking of *incoming* frames — see requireMaskedIncoming.
  • Method Details

    • isOpen

      public boolean isOpen()
    • closeCode

      public int closeCode()
    • request

      public Request request()
      The request that upgraded this connection, or null — see the 4-arg constructor.
    • isSecure

      public boolean isSecure()
      Whether this connection is TLS (WSS). Delegates to the upgrading request's Request.isSecure() rather than tracking the socket a second time — the request already carries it. false if this session has no backing request (e.g. one opened in WS *client* mode via the 5-arg constructor with request == null).
    • sslSession

      public SSLSession sslSession()
      TLS session for this connection, or null for plain WS or no backing request.
    • sendText

      public void sendText(byte[] utf8, int off, int len) throws IOException
      Throws:
      IOException
    • send

      public void send(byte[] payload, int off, int len) throws IOException
      Throws:
      IOException
    • close

      public void close(int code) throws IOException
      Sends a CLOSE frame exactly once. No flush needed — out is the raw socket OutputStream (not buffered); each write goes directly to the kernel send buffer, and TCP_NODELAY ensures it's transmitted immediately.
      Throws:
      IOException
    • readFrame

      public boolean readFrame(WebSocketFrame frame) throws IOException
      Reads the next complete message, reassembling continuation frames and delivering control frames (CLOSE/PING/PONG) as soon as they arrive — RFC 6455 §5.4 explicitly permits a control frame to interleave with a fragmented data message, and this must not disturb the data message's in-progress reassembly.
      Returns:
      false only on a clean EOF between messages (the peer closed the TCP connection without sending a CLOSE frame); an EOF in the middle of a frame is a protocol violation and throws, it is not reported as false.
      Throws:
      WebSocketProtocolException - on any RFC 6455 violation (bad opcode, unmasked/masked frame when the opposite was required, oversized control frame, fragmented control frame, message exceeding the buffer) — carries the correct close code.
      IOException
    • sendPong

      public void sendPong(WebSocketFrame ping) throws IOException
      Throws:
      IOException
    • echo

      public void echo(WebSocketFrame frame) throws IOException
      Throws:
      IOException
    • closeFromPeer

      public void closeFromPeer(WebSocketFrame frame)
    • forceClose

      public void forceClose()