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.

Reply via email to