Decided to take a stab at a list of "requirements"... What do people want from a WebSocket client?
First, the baseline requirements: (If you don't provide 100% of this list you have a inadequate implementation): Initiate a connection - simple connect (URI) - advanced connect (HttpClient) - ability to manage SSL configuration - ability to manage cookies - ability to manage authentication - ability to manage arbitrary HTTP headers - configure websocket handshake request values - subprotocol list - extension list - origin - configure websocket implementation behavior (necessary for working with whole message receive!) - max text message size (ignored for streaming behavior) - max binary message size (ignored for streaming behavior) Events: - connected (event / single) - received data [TEXT|BINARY] (event / [0..n]) - receive whole TEXT void onText(String) - receive whole BINARY void onBinary(ByteBuffer buf) - received control [PING|PONG] (event / [0..n]) - valid signatures - void onPing(ByteBuffer payload) Note: implementation automatically responds to received PING with PONG of identical payload - void onPong(ByteBuffer payload) - closed (event / single / terminal) - knowledge about why? - was this a local close or remote close? - was the close clean? (abnormal) - I/O error (event / [0..n]) - connection timeout - unable to write (connection closed?) - bad data (protocol violation detected locally) Queries: - is still open/connected - remote InetAddress - local InetAddress - remote endpoint URI - websocket handshake requested - subprotocol list (optional) - extension list (optional) - origin (optional) - websocket handshake negotiated - subprotocol selected - extensions in use Actions: - send data - send whole TEXT Local UTF8 validation required! - valid signatures .send(java.lang.CharSequence text) .send(java.nio.CharBuffer textBuffer) - send whole BINARY - valid signatures .send(byte[] buf) .send(byte[] buf, int offset, int len) .send(java.nio.ByteBuffer) - send control - send PING - close - friendly close actions .close() - no status code, no reason. an IOException is not possible from this use. Always results in close and/or disconnect. .close(int) - status code only, no reason. an IOException can result from protocol violation from bad statusCode use (1005/1006) .close(int, String) - status code and reason. an IOException can result from protocol violation Note: "close" event only notified if remote has closed too. - harsh close .disconnect() Note: "close" event is called with abnormal close (1006) Next: the advanced topics: Events: - receive partial TEXT void onText(String partial, boolean finished) Note: implementation needs to be UTF8 codepoint aware to only notify whole codepoints, keeping partial codepoints for next receive. it is possible to have an empty partial with finished==true - receive partial BINARY void onBinary(ByteBuffer partial, boolean finished) Note: it is possible to have an empty partial with finished==true Actions: - send async Whole TEXT MUST have backpressure support! consider JSR166 java.util.concurrent.Flow - send async Whole BINARY MUST have backpressure support! consider JSR166 java.util.concurrent.Flow - send stream TEXT Proposed signature: .sendTextStream(ReadableByteChannel channel) Note: implementation makes the call when to read from the provided channel and when not to based on backpressure Bonus: wire up existing nio channels - send stream BINARY Proposed signature: .sendBinaryStream(ReadableByteChannel channel) Note: implementation makes the call when to read from the provided channel and when not to based on backpressure Bonus: you can wire up nio channels just like existing nio behavior Really advanced topics: Extensions: - Send TEXT and Send BINARY methods should have the ability to provide hints to the outgoing frame extension handling mechanism. - MUST support standard RFC Extension negotiation. - Local Extensions should be supported as well (for extensions that don't need negotiation with the remote, but can run entirely on local side) eg: Fragment extension (automatically break up large messages by upper fragment size). Identity extensions (to have 3rd party libraries pay attention to what is being sent) Debug extensions / Multicast send/receive / etc ... Extension SPI: - This is possible, Jetty introduced its SPI in Jetty 9.1.0 (Nov 2013) Proposed API: public interface IncomingFrames { void incomingFrame(Frame); } public interface OutgoingFrames { void outgoingFrame(Frame, CallbackFuture); } public interface Extension extends IncomingFrames, OutgoingFrames{ String getToken(); String getParams(); String negotiate(String params); int getRSV(); void setNextIncomingFrames(IncomingFrames incoming); void setNextOutgoingFrames(OutgoingFrames outgoing); } What you need to provide: - Extension Negotiation - Generate Extension ID (token/name + params) - Given Requested Extension ID (token/name + params), determine negotiation Proposed pseudo code for implementation: IncomingFrames nextIncoming = getFrameParser(); OutgoingFrames nextOutgoing = getFrameGenerator(); ListIterator<Extension> exts: response.getExtensions().listIterator(); // Connect outgoings while(exts.hasNext()) { Extension ext = exts.next(); ext.setNextOutgoingFrames(nextOutgoing); nextOutgoing = ext; } // Connect incomings while(exts.hasPrevious()) { Extension ext = exts.previous(); ext.setNextIncomingFrames(nextIncoming); nextIncoming = ext; } // Wire up to connection connection.setOutgoingFrames(nextOutgoing); connection.setIncomingFrames(nextIncoming); - Incoming Frame manipulation The incoming frame can result in [0..n] nextIncoming.incomingFrame(Frame) calls - Outgoing Frame manipulation The outgoing frame can result in [0..n] nextOutgoing.outgoingFrame(Frame,CallbackFuture) calls. Note: if extension fragments the frame further, the Callback for completion should also be handled intelligently for the finished frame write.