Class WebSocketSession
One websocket connection: the decoder that turns bytes into messages, and the handle an application sends through.
Why the decoder is a state machine rather than a loop over whole frames
A frame arrives in as many reads as the network chooses. The header may be split
across two of them, the mask key across four, and a 10MB payload across
thousands -- so every piece of state that spans a read boundary lives in a field
here: how much of the current payload is still owed, how far into the mask key
the next byte lands, whether a message is open and which opcode started it, and
how far a UTF-8 character got. pump() consumes whatever has arrived and
stops cleanly when it wants more, which is what lets the same decoder be driven
by a blocking loop on a virtual thread and by one reactor turn on a pool worker.
Why this buffer is its own
The HTTP read path borrows a per-host-thread array and parses in place. That is
free for a request, which is read, served and written without stopping -- and
exactly wrong for a websocket, which stops between every message by design. A
borrowed buffer held across a park has to be copied at every park, so this class
owns its buffer from the moment of upgrade and never touches Conn.buffer
again. See HttpServer.tryUpgrade(HttpServer.Conn, int, long, HttpServer.Request, Span).
Why sending takes a lock and checks a flag three times
Broadcast is the normal reason to run a websocket server, so a send can come
from any thread. Two writes to one descriptor would interleave frames, hence
writeLock. Worse, the descriptor can be closed under a sender and its NUMBER
reused immediately -- so dead is set before the close and checked on the way
in, after registering as a writer, and again under the lock. A handle whose
connection has gone is inert rather than dangerous.
-
Method Summary
Modifier and TypeMethodDescriptionvoidabort()Drops the connection with no closing handshake.voidclose()A normal close, code 1000, no reason.voidStarts the closing handshake.getHandshakeHeader(String name) A header from the handshake request, case-insensitively.longgetId()A number unique within this process, for logging and for keying a registry.getPath()The path the client upgraded on, with no query string.getQuery()The query string, or null when there was none.The negotiated subprotocol, or null when none was.booleanisOpen()Whether this connection is still usable.voidsendBinary(byte[] value) voidsendBinary(byte[] value, int offset, int length) voidsendPing(byte[] payload, int offset, int length) voidsendPong(byte[] payload, int offset, int length) voidvoidsetAttachment(Object value) Anything the endpoint wants to keep per connection.
-
Method Details
-
getId
public long getId()A number unique within this process, for logging and for keying a registry. -
getPath
The path the client upgraded on, with no query string. -
getQuery
The query string, or null when there was none. -
getHandshakeHeader
-
getSubprotocol
The negotiated subprotocol, or null when none was. -
isOpen
public boolean isOpen()Whether this connection is still usable. -
getAttachment
-
setAttachment
Anything the endpoint wants to keep per connection. -
sendText
- Throws:
IOException
-
sendBinary
- Throws:
IOException
-
sendBinary
- Throws:
IOException
-
sendPing
- Throws:
IOException
-
sendPong
- Throws:
IOException
-
close
public void close()A normal close, code 1000, no reason. -
close
Starts the closing handshake.
Sends a Close frame and then waits for the peer's, which is what lets the last message already in flight arrive. A peer that never answers is shed by the connection's read timeout rather than held forever.
-
abort
public void abort()Drops the connection with no closing handshake. For a peer already lost.
-