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