On the topic of error handling. A high level API doesn't need to report
each individual error with sending. So firstly, it is impossible to report
*all* errors with sending, since it's impossible to know, once you send a
message across the network, whether it got there or not. So if an
application developer has a requirement to handle errors in sending (and
realistically, there is never a business require to handle just the errors
that can be detected in sending, it's always about handling all errors), do
we expect them to implement that logic in two places, both on the sending
side to handle errors that can be detected on the sending side, and then
additionally write logic to handle errors that can't be detected on the
sending side (such as, for example, having the remote side send ACKs, and
track the ACKs on the receiving end)? I doubt it, having to handle logic on
both sides is going to mean the application developer has to spend more
time implementing boiler plate that could otherwise be spent solving their
actual business problem.  If an application developer needs to handle
errors in sending messages, it would be much simpler for them to treat both
types of errors in the same way. And hence, a high level API should expose
detectable errors in sending on the receiving end, by cancelling upstream,
terminating the WebSocket with an error, and then emitting the error in the
downstream onError.

When it comes to protocol specific concerns - a high level API should not
expose that to developers.  A developer should not be allowed to send a
text message between two split binary frames. A developer should not be
responsible for implementing the closing handshake protocol. That is the
responsibility of a high level API. WebSocket close messages are designed
such that high level APIs don't need to expose them to end developers - you
have one status for success, and then many statuses for close. This means,
an application developer can signal a normal close by simply terminating
the stream, and can signal any of the other closes by terminating with an
error that gets mapped to an error status code - likewise, closes received
can be mapped in the same way.  And, most of the error codes are for
protocol level errors anyway that a user should never be generating and
can't really handle. So I don't think, if providing a high level API, it
makes any sense to expose close messages or the close handshake to
application developers - the WebSocket protocol by design is not meant to
be used that way.

For fragmented frames, once again, I don't think the designers of the
WebSocket protocol ever intended that this protocol level detail would ever
be exposed to application developers. My reading of the spec is that the
reason fragmentation is allowed is to allow endpoints to work with fixed
buffer sizes, allowing messages to be split if they choose. In fact, this
is exactly what the RFC says:

The primary purpose of fragmentation is to allow sending a message
> that is of unknown size when the message is started without having to
> buffer that message.  If messages couldn't be fragmented, then an
> endpoint would have to buffer the entire message so its length could
> be counted before the first byte is sent.  With fragmentation, a
> server or intermediary may choose a reasonable size buffer and, when
> the buffer is full, write a fragment to the network.


https://tools.ietf.org/search/rfc6455#section-5.4

This is a protocol level detail, not something that is supposed to be
exposed to application developers. It is reasonable to expect that an
implementation will buffer and put the fragments back together, up to a
configured buffer size, for application consumption. If a message does
exceed that buffer size, then the implementation can close the socket with
the close code designed exactly for that purpose - 1009.

I don't think a high level API should seek to make every feature of
WebSockets available just because it's possible in the protocol - I think
that would be over-engineering, over complicating things for application
developers. I think a high level API should be focused on how the protocol
is intended to be used by application developers.

On 24 February 2018 at 22:55, Chris Hegarty <chris.hega...@oracle.com>
wrote:

> Rossen,
>
> On 23 Feb 2018, at 21:15, Rossen Stoyanchev <rstoyanc...@pivotal.io>
> wrote:
>
> hi Pavel,
>
> On Thu, Feb 22, 2018 at 8:43 AM, Pavel Rappo <pavel.ra...@oracle.com>
> wrote:
>
>>
>> 1. If there is no error reporting, then how would the user know if there's
>> something wrong with the data they send? I'm talking about
>> incomplete/malformed
>> UTF-8, fragments intermixing, sending messages after a Close message has
>> been
>> sent, etc. How should the user troubleshoot a situation where the
>> WebSocket's
>> internal Subscriber cancels the subscription unexpectedly? We can't
>> perform all
>> the checks during a message creation. Some messages could not be checked
>> up
>> until they appear in some context. For example, a text fragment is a okay
>> per
>> se. But if it appears in the midst of a fragmented binary message, it is
>> not
>> okay. Should the API not allow fragmented (partial) messages then? Or
>> should we
>> provide error reporting out-of-band in relation to Flow? If we allow only
>> whole
>> messages, then we are not flexible enough to support streaming. In other
>> words
>> the case of an arbitrarily large message that does not have to be
>> assembled
>> prior to consumption.
>>
>
> Charset encoding issues aside, are these represented with specific
> exceptions, and how much can the application do with them?
>
>
> An application can handle, and possibly recover.
>
> From WebSocketMessage [1]:
>
>     /**
>      * Return the message payload as UTF-8 text. This is a useful for text
>      * WebSocket messages.
>      */
>     public String getPayloadAsText() {
> byte[] bytes = new byte[this.payload.readableByteCount()];
> this.payload.read(bytes);
> return new String(bytes, StandardCharsets.UTF_8);
>     }
>
> This seems woefully inadequate, and underspecified. Not to mention what
> happens if the message is not text. The Java SE API attempts to avoid
> such.
>
>
> I don't know the fragments intermixing issue but can the application
> recover from that?
>
>
> With the Java SE API, yes.
>
> It is of course more useful for the API to expose error information for
> each sent message, but if those are low level errors, treated as terminal,
> then only the first one practically matters.
>
>
> That is not always the case. And this is a low-level API.
>
> I'm unsure about the exact meaning of fragments​. JSR-356 has partial
> messages, which if I recall were explicitly called out in the spec as not
> being different from (and not 1-for-1 with) WebSocket fragments. I'll
> assume that you mean the same (i.e. the send methods that take an extra
> isLast flag). WebSocket is not an application-level protocol and in my
> experience, streaming by splitting large content, is a higher level
> concern. For example the JavaScript client for STOMP over WebSocket some
> time ago started splitting larger STOMP messages along 16K boundaries to
> work better with the default buffer sizes of most servers. I'm not saying
> such feature shouldn't be present but I don't think it's a must.
>
>
> Why preclude it, when it is not necessary to do so?
>
> If required, one way to represent it is to allow writing with a
> Publisher<Publisher<ByteBuffer>>.
>
>
> Yuck!
>
> 2. How should the user close their side of the communication? There seems
>> to be
>> a mismatch in the protocols. WebSocket is bidirectional and relies on
>> Close
>> messages. Flow is unidirectional-ish and relies on cancellation through
>> onComplete/onError and cancel signals. If the user has no more messages to
>> publish, the user may wish to send a Close message with a status code and
>> a
>> reason. If instead we rely on onComplete/onError being called then we
>> loose the
>> ability to provide these extra pieces of information. What should we
>> choose
>> here? To not allow a Close message to be sent or to require the user to
>> signal
>> onComplete() after a Close message has been signalled through onNext().
>> Maybe
>> there should not be a representation of a Close message at all. Maybe we
>> should
>> leave it as a protocol low-level detail. But then, how should the API
>> deliver
>> the received status code and the reason of a Close message it receives?
>> Should
>> it translate the received Close message to onComplete()/onError()
>> depending on
>> this message's contents?
>>
>
> I think the API can provide such a close(CloseStatus) method. That would
> result in a cancellation of the write Publisher.
>
>
>> 3. WebSocket protocol mandates a peer should send a Close message as soon
>> as
>> practical after receiving one. In the current API it is implemented using
>> a
>> CompletionStage returned from the WebSocket.Listener.onClose method. How
>> should
>> we implement this using Flow?
>>
>
> It is hard to discuss on this level of detail without a POC.
>
>
> We have gone through many POC’s over the past 3 or more years, bringing
> us to where we are today. Much of this work and is archived on the net-dev
> mailing list, if you wish to review it. It’s the only way to validate API
> suggestions.
>
> I'd assume the API exposes a "read" Publisher<WebSocket>, a way to send
> with a "write" Publisher<WebSocket> and some Handler/Listener that returns
> Publisher<Void> (or CompletionStage) for when handling is complete. The
> "read" Publisher would complete when the server closes. The Publisher<Void>
> from the Handler/Listener would complete when all is handled.
>
>
>
>> Sure one may recycle buffers transparently. But only if the
>> buffer type supports it. io.netty.buffer.PooledByteBufAllocator provides
>> instances of io.netty.buffer.ByteBuf. Not java.nio.ByteBuffer. The latter
>> one
>> does not have any release/free/dispose/etc. methods and semantics.
>>
>
> ​If the API exposed some Consumer<ByteBuffer> callback to be applied to
> buffers that have been written, then such a pool could also be implemented
> externally.
> ​​
>
>> P.S. How may purely RS WebSocket APIs are there? If there are some, we
>> can probably
>> learn from their experience and make things a bit better. Otherwise I
>> don't see
>> why we should create one more of them. Would you rather have something
>> low-level, you could then implement your high-level API that is most
>> relevant to
>> your case?
>>
>
> You can see the reactive WebSocket API facade (abstracting different
> clients) in the Spring Framework:
> https://docs.spring.io/spring/docs/current/javadoc-api/org/
> springframework/web/reactive/socket/package-summary.html
>
>
> This is an example of just one way that WebSocket could be modelled.
> It appears reasonably straight forward, but restrictive in terms of the
> underlying protocol, not exposing partial messages for example,
> requiring an additional allocation of a message type for each message
> sent / received. I would expect that such a higher-level API could be
> implemented on top of the Java SE API without too much work.
>
> -Chris.
>
> [1] https://github.com/spring-projects/spring-framework/
> blob/master/spring-webflux/src/main/java/org/springframework/web/reactive/
> socket/WebSocketMessage.java
>
>


-- 
*James Roper*
*Senior Octonaut*

Lightbend <https://www.lightbend.com/> – Build reactive apps!
Twitter: @jroper <https://twitter.com/jroper>

Reply via email to