Preliminary RFR JDK-8159053: Improve onPing/onClose behavior
Pavel Rappo
pavel.rappo at oracle.com
Wed Jul 13 13:05:37 UTC 2016
It looks like we've agreed on everything except for the status codes and the
spec for what WebSocket does after a CS returned from onClose completes.
May I propose the following spec?
/**
* Receives a Close message.
*
...
*
* <p> A Close message consists of a status code and a reason for
* closing. The status code is an integer in the range {@code 1000 <=
* code <= 65535}. The {@code reason} is a short string that has an
* UTF-8 representation not longer than {@code 123} bytes. For more
* details on Close message, status codes and reason see RFC 6455 sections
* <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">5.5.1. Close</a>
* and
* <a href="https://tools.ietf.org/html/rfc6455#section-7.4">7.4. Status Codes</a>.
*
* <p> After the returned {@code CompletionStage} has completed
* (normally or exceptionally), the {@code WebSocket} finishes the
* Closing handshake by sending an appropriate Close message. This
* implementation sends a Close message with the same code this one has
* and an empty reason.
*
...
*
*/
CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason)
and
/**
* Sends a Close message with the given status code and the reason.
*
...
*
* <p> The {@code statusCode} is an integer in the range {@code 1000 <= code
* <= 4999}. However, not all status codes may be legal in some
* implementations. {@code 1000} (Normal Closure) is always legal and {@code
* 1002}, {@code 1003}, {@code 1005}, {@code 1006}, {@code 1007}, {@code
* 1009}, {@code 1010}, {@code 1012}, {@code 1013} and {@code 1015} are are
* always illegal codes.
*
* <p> The {@code reason} is a short string that must have an UTF-8
* representation not longer than {@code 123} bytes. For more details on
* Close message, status codes and reason see RFC 6455 sections
* <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">5.5.1. Close</a>
* and
* <a href="https://tools.ietf.org/html/rfc6455#section-7.4">7.4. Status Codes</a>.
*
...
*
* @throws IllegalArgumentException
* if the {@code statusCode} has an illegal value, or
* {@code reason} doesn't have an UTF-8 representation not longer
* than {@code 123} bytes
*
* @see #sendClose()
*/
CompletableFuture<WebSocket> sendClose(int statusCode, String reason);
Oh dear, I know Simone won't probably like it, but please bear with me while I'm
trying to explain the rationale behind all this.
1. onClose now clearly defines what happens when returned CS completes. And the
default implementation follows the RFC's suggestion:
When sending a Close frame in response, the endpoint typically echos the
status code it received.
2. The asymmetry between range of codes [1000, 65535] eligible for onClose and
range of codes [1000, 4999] eligible for sendClose is due applying Robustness
Principle [1] to spec's vagueness [2], [3] in this area.
In other words, it's playing safe to accept codes from [5000, 65535], but at the
same time not allowing to send them.
3. Allowing/rejecting particular codes is hard. Having carefully reviewed all
defined codes I would propose to go with this:
1000 is always allowed (along with the no-args sendClose() that sends an empty Close message)
1002, 1003, 1005, 1006, 1007, 1009, 1010, 1012, 1013 and 1015 are always rejected
Everything else is up to an implementation. The reason is that otherwise we
could spend too much time discussing these codes, simply because except for the
ones above their description is too vague and differ in RFC and IANA. So if
anybody wants to rathole, let's fork a separate thread for it.
Here's a small notation that might help with reasoning about status codes:
S: @implSpec N: @implNote
+: allow -: reject
Here we go:
S+ 1000 Normal Closure
N+ 1001 Going Away
What's the difference between this and 1000? It seems to be way too
subtle understand from a short description in the javadoc.
As far as I understand it's designed a premature close (cancellation).
Maybe it's okay to send it in the midst of receiving something?
S- 1002 Protocol error
Should not be allowed as the only one who decides what the protocol
error is, is the WebSocket implementation.
S- 1003 Unsupported Data
Ideally should be automatically sent by WebSocket in case the
corresponding method is not overridden and a message of this type has
been received.
It could've been done by having a separate handlers for all types of
events instead of a single Listener type... but we've chosen a different
way. Maybe we could configure this behaviour later in Builder.
N- 1004 Reserved
From the RFC:
"Reserved. The specific meaning might be defined in the future."
I think it's ok to reject it for now, but let's not add this to the
spec just yet.
S- 1005 No Status Rcvd
It's not going to be accepted and translated to an empty Close message
by the impl. Because it's way too deceptive and non-intuitive.
S- 1006 Abnormal Closure
S- 1007 Invalid frame payload data
As with 1002, this should be allowed to be set only by WebSocket.
N+ 1008 Policy Violation
This one is not that clear. Supertype for 1003/1009. What is a policy?
S- 1009 Message Too Big
As with 1003 and ideal candidate to be automatically set by WebSocket,
given that maximum allowed size is configurable in Builder.
It can be done manually today, but with extensions user might not be
able to receive a fragment, and the message will have to be fully
assembled before it relayed to Listener.
S- 1010 Mandatory Ext.
Same as with 1002, 1007, 1003, etc. Should be only set by WebSocket.
N+ 1011 Internal Error
IANA describes it as Internal Error, however RFC's initial text is this:
"1011 indicates that a server is terminating the connection because
it encountered an unexpected condition that prevented it from
fulfilling the request."
So does it have to be linked with some request? Or it can also mean an
asynchronous error, not caused by any of the previous requests?
S- 1012 Service Restart
S- 1013 Try Again Later
These two are new ones. They have been added to IANA registry after the
RFC has been published.
It looks like they are more about the server [4].
N- 1014 Unassigned
Same as with 1004. Reject, but don't mention in the soec.
S- 1015 TLS handshake
N- 1016-3999 Unassigned
This ones should not be allowed for the same reason 1004 and 1014 are
not allowed. Reject them, but not mention in the spec.
N+ 4000-4999 Reserved for Private Use
Use it if you know what they mean to a particular server.
Phew!
Thanks,
-Pavel
--------------------------------------------------------------------------------
[1] https://en.wikipedia.org/wiki/Robustness_principle
[2] https://tools.ietf.org/html/rfc6455#section-7.4.2
[3] http://autobahn.ws/testsuite/reports_20131013/clients/index.html#case_desc_7_13_1
[4] http://www.ietf.org/mail-archive/web/hybi/current/msg09670.html
> On 17 Jun 2016, at 16:15, Pavel Rappo <pavel.rappo at oracle.com> wrote:
>
> Hi,
>
> Could you please [*] review the following change for the upcoming JDK-8159053?
> This change addresses:
>
> 1. Listener.onClose/onPing behaviour, making the implementation fully*
> responsible of protocol compliance. So reactions on onClose/onPing cannot be
> overridden/redefined in a Listener
>
> 2. Simpler representation of the Close message.
>
> /**
> * Receives a Pong message.
> *
> * <p> A Pong message may be unsolicited or may be received in response
> * to a previously sent Ping. In the latter case, the contents of the
> * Pong is identical to the originating Ping.
> *
> * <p> The message will consist of not more than {@code 125} bytes:
> * {@code message.remaining() <= 125}.
> *
> * <p> The {@code onPong} method is invoked zero or more times in
> * between {@code onOpen} and ({@code onClose} or {@code onError}).
> *
> * <p> If an exception is thrown from this method or the returned {@code
> * CompletionStage} completes exceptionally, then {@link
> * #onError(WebSocket, Throwable) onError} will be invoked with this
> * exception.
> *
> * @implSpec The default implementation behaves as if:
> *
> * <pre>{@code
> * webSocket.request(1);
> * return CompletableFuture.completedStage(null);
> * }</pre>
> *
> * @param webSocket
> * the WebSocket
> * @param message
> * the message
> *
> * @return a CompletionStage that completes when the message processing
> * is done; or {@code null} if already done
> */
> default CompletionStage<?> onPong(WebSocket webSocket,
> ByteBuffer message) {
> webSocket.request(1);
> return null;
> }
>
> /**
> * Receives a Close message.
> *
> * <p> Once a Close message is received, the server will not send any
> * more messages.
> *
> * <p> A Close message consists of a status code and a reason for
> * closing. The status code is an integer in the range {@code 1000 <=
> * code <= 65535}. The reason is a short string that has an UTF-8
> * representation not longer than {@code 123} bytes.
> *
> * <p> For more information on status codes and reasons see
> * <a href="https://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455, 7.4. Status Codes</a>.
> *
> * <p> {@code onClose} is the last invocation on the {@code Listener}.
> * It is invoked at most once, but after {@code onOpen}. If an exception
> * is thrown from this method, it is ignored.
> *
> * <p> After a Close message has been received, the implementation will
> * close the connection automatically. However, the client is allowed to
> * finish sending the current sequence of partial message first. To
> * facilitate it, the WebSocket implementation expects this method to
> * return a {@code CompletionStage}. The implementation then will
> * attempt to close the connection at the earliest of, either the
> * completion of the returned {@code CompletionStage} or the last part
> * of the current message has been sent.
> *
> * @implSpec The default implementation behaves as if:
> *
> * <pre>{@code
> * return CompletableFuture.completedStage(null);
> * }</pre>
> *
> * @param webSocket
> * the WebSocket
> * @param statusCode
> * the status code
> * @param reason
> * the reason
> *
> * @return a CompletionStage that when completed will trigger closing of
> * the WebSocket
> */
> default CompletionStage<?> onClose(WebSocket webSocket,
> int statusCode,
> String reason) {
> return null;
> }
>
> /**
> * Sends a Close message with the given status code and the reason.
> *
> * <p> Returns a {@code CompletableFuture<WebSocket>} which completes
> * normally when the message has been sent or completes exceptionally if an
> * error occurs.
> *
> * <p> The {@code statusCode} is an integer in the range {@code 1000 <= code
> * <= 4999}, except for {@code 1004}, {@code 1005}, {@code 1006} and {@code
> * 1015}. The {@code reason} is a short string that must have an UTF-8
> * representation not longer than {@code 123} bytes.
> *
> * <p> For more information about status codes see
> * <a href="https://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455, 7.4. Status Codes</a>.)
> *
> * <p> If a Close message has been already sent or the {@code WebSocket} is
> * closed, then invoking this method has no effect and returned {@code
> * CompletableFuture} completes normally.
> *
> * <p> The returned {@code CompletableFuture} can complete exceptionally
> * with:
> * <ul>
> * <li> {@link IOException}
> * if an I/O error occurs during this operation
> * </ul>
> *
> * @param statusCode
> * the status code
> * @param reason
> * the reason
> *
> * @return a CompletableFuture with this WebSocket
> *
> * @throws IllegalArgumentException
> * if the {@code statusCode} has an illegal value, or
> * {@code reason} doesn't have an UTF-8 representation not longer
> * than {@code 123} bytes
> *
> * @see #sendClose()
> */
> CompletableFuture<WebSocket> sendClose(int statusCode, String reason);
>
> /**
> * Sends an empty Close message.
> *
> * <p> Returns a {@code CompletableFuture<WebSocket>} which completes
> * normally when the message has been sent or completes exceptionally if an
> * error occurs.
> *
> * <p> If a Close message has been already sent or the {@code WebSocket} is
> * closed, then invoking this method has no effect and returned {@code
> * CompletableFuture} completes normally.
> *
> * <p> The returned {@code CompletableFuture} can complete exceptionally
> * with:
> * <ul>
> * <li> {@link IOException}
> * if an I/O error occurs during this operation
> * </ul>
> *
> * @return a CompletableFuture with this WebSocket
> */
> CompletableFuture<WebSocket> sendClose();
>
> Thanks,
> -Pavel
>
> --------------------------------------------------------------------------------
> [1] Please excuse me for not publishing this in a form of a webrev. The reason
> is that currently there are too many outstanding changes in the queue that would
> simply obscure the review.
More information about the net-dev
mailing list