http-client-branch: (WebSocket) API update http-client-branch
authorprappo
Tue, 21 Nov 2017 12:27:45 +0300
branchhttp-client-branch
changeset 55842 cb8fcde5b5c8
parent 55841 5f0b66e83dfa
child 55843 1e3a22efaefd
child 55844 dbcbcda0e413
http-client-branch: (WebSocket) API update
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocket.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/Receiver.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketImpl.java
test/jdk/java/net/httpclient/websocket/ConnectionHandover.java
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java	Mon Nov 20 18:36:57 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java	Tue Nov 21 12:27:45 2017 +0300
@@ -512,17 +512,9 @@
      *
      * @implNote Both builder and {@code WebSocket}s created with it operate in
      * a non-blocking fashion. That is, their methods do not block before
-     * returning a {@code CompletableFuture}. Asynchronous tasks executed in
+     * returning a {@code CompletableFuture}. Asynchronous tasks are executed in
      * this {@code HttpClient}'s executor.
      *
-     * <p> {@code WebSocket} does not allow to send Text messages that are
-     * partial UTF-16 sequences. If such a sequence is passed, a
-     * {@code CompletableFuture} returned from {@link WebSocket#sendText} will
-     * complete exceptionally with {@code IOException}.
-     * Similarly, {@code WebSocket} invokes
-     * {@link WebSocket.Listener#onText Listener.onText} with messages which
-     * are complete UTF-16 sequences.
-     *
      * <p> When a {@code CompletionStage} returned from
      * {@link WebSocket.Listener#onClose Listener.onClose} completes,
      * the {@code WebSocket} will send a Close message that has the same code
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocket.java	Mon Nov 20 18:36:57 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocket.java	Tue Nov 21 12:27:45 2017 +0300
@@ -26,7 +26,6 @@
 package jdk.incubator.http;
 
 import java.io.IOException;
-import java.net.ProtocolException;
 import java.net.URI;
 import java.nio.ByteBuffer;
 import java.time.Duration;
@@ -34,50 +33,54 @@
 import java.util.concurrent.CompletionStage;
 
 /**
- * A WebSocket client conforming to RFC&nbsp;6455.
+ * A WebSocket client.
  * {@Incubating}
  *
- * <p> A {@code WebSocket} provides full-duplex communication over a TCP
- * connection.
+ * <p> To create a {@code WebSocket} use the {@link HttpClient#newWebSocketBuilder}
+ * method. To close a {@code WebSocket} use {@code sendClose} or {@code abort}
+ * methods.
+ *
+ * <p> WebSocket messages are sent through a {@code WebSocket} and received
+ * through this {@code WebSocket}'s {@code Listener}. Messages can be sent until
+ * the output is closed and received until the input is closed.
+ * A {@code WebSocket} whose output and input are both closed may be considered
+ * closed. To check these states use {@link #isOutputClosed()} and
+ * {@link #isInputClosed()}.
  *
- * <p> To create a {@code WebSocket} use a {@linkplain HttpClient#newWebSocketBuilder(
- * URI, Listener) builder}. Once a {@code WebSocket} is built, it's ready
- * to send and receive messages. When the {@code WebSocket} is no longer needed
- * it must be closed: a Close message must both be {@linkplain #sendClose
- * sent} and {@linkplain Listener#onClose(WebSocket, int, String) received}.
- * The {@code WebSocket} may be also closed {@linkplain #abort() abruptly}.
+ * <p> Methods that send messages return {@code CompletableFuture} which will
+ * complete normally if the message is sent or will complete exceptionally if an
+ * error occurs.
  *
- * <p> Once closed the {@code WebSocket} remains {@linkplain #isClosed() closed}
- * and cannot be reopened.
- *
- * <p> Messages of type {@code X} (where {@code X} is one of: Text, Binary,
- * Ping, Pong or Close) are sent and received asynchronously through the {@code
- * WebSocket.send{X}} and {@link WebSocket.Listener}{@code .on{X}} methods
- * respectively. Each method returns a {@link CompletionStage} which completes
- * when the operation has completed.
+ * <p> To receive a message, request it first. If {@code n} messages are
+ * requested, the listener will receive up to {@code n} more invocations of
+ * designated methods from the {@code WebSocket}. To request messages use
+ * {@link #request(long)}. Request is an additive operation, that is
+ * {@code request(n)} followed by {@code request(m)} is equivalent to
+ * {@code request(n + m)}.
  *
- * <p> Note that messages (of any type) are received only if {@linkplain
- * #request(long) requested}.
+ * <p> When sending or receiving a message in parts, a whole message is
+ * transferred as a sequence of one or more invocations where the last
+ * invocation is identified via an additional method argument.
  *
- * <p> One outstanding send operation is permitted. No further send operation
- * can be initiated before the previous one has completed. When sending, a
- * message must not be modified until the returned {@link CompletableFuture}
- * completes (either normally or exceptionally).
+ * <p> Unless otherwise stated, {@code null} arguments will cause methods
+ * of {@code WebSocket} to throw {@code NullPointerException}, similarly,
+ * {@code WebSocket} will not pass {@code null} arguments to methods of
+ * {@code Listener}.
  *
- * <p> Text and Binary messages can be sent and received as a whole or in parts.
- * A whole message is transferred as a sequence of one or more invocations of a
- * corresponding method where the last invocation is identified via an
- * additional method argument.
+ * @implSpec Methods of {@code WebSocket} are failure-atomic in respect to
+ * {@code NullPointerException}, {@code IllegalArgumentException} and
+ * {@code IllegalStateException}. That is, if a method throws said exception or
+ * a {@code CompletableFuture} returned from a method completes exceptionally
+ * with it, the {@code WebSocket} will behave as if the method has not been
+ * invoked at all.
  *
- * <p> If the message is contained in a {@link ByteBuffer}, bytes are considered
- * arranged from the {@code buffer}'s {@link ByteBuffer#position() position} to
- * the {@code buffer}'s {@link ByteBuffer#limit() limit}.
+ * <p> A {@code WebSocket} invokes methods of its listener in a thread-safe
+ * manner.
  *
- * <p> Unless otherwise stated, {@code null} parameter values will cause methods
- * and constructors to throw {@link NullPointerException}.
- *
- * @implNote This implementation's methods do not block before returning
- * a {@code CompletableFuture}.
+ * <p> {@code WebSocket} handles Ping and Close messages automatically (as per
+ * RFC 6455) by replying with Pong and Close messages respectively. If the
+ * listener receives Ping or Close messages, no mandatory actions from the
+ * listener are required.
  *
  * @since 9
  */
@@ -205,91 +208,31 @@
     }
 
     /**
-     * A listener for events and messages on a {@code WebSocket}.
+     * The receiving interface of {@code WebSocket}.
      * {@Incubating}
      *
-     * <p> Each method of {@code Listener} corresponds to a type of event or a
-     * type of message. The {@code WebSocket} argument of the method is the
-     * {@code WebSocket} the event has occurred (the message has been received)
-     * on. All methods with the same {@code WebSocket} argument are invoked in a
-     * sequential
-     * (and <a href="../../../java/util/concurrent/package-summary.html#MemoryVisibility">happens-before</a>)
-     * order, one after another, possibly by different threads.
-     *
-     * <ul>
-     * <li> {@link #onOpen(WebSocket) onOpen} <br>
-     * This method is invoked first.
-     * <li> {@link #onText(WebSocket, CharSequence, WebSocket.MessagePart)
-     * onText}, {@link #onBinary(WebSocket, ByteBuffer, WebSocket.MessagePart)
-     * onBinary}, {@link #onPing(WebSocket, ByteBuffer) onPing} and {@link
-     * #onPong(WebSocket, ByteBuffer) onPong} <br>
-     * These methods are invoked zero or more times after {@code onOpen}.
-     * <li> {@link #onClose(WebSocket, int, String) onClose}, {@link
-     * #onError(WebSocket, Throwable) onError} <br>
-     * Only one of these methods is invoked, and that method is invoked last.
-     * </ul>
-     *
-     * <p> Messages received by the {@code Listener} conform to the WebSocket
-     * Protocol, otherwise {@code onError} with a {@link ProtocolException} is
-     * invoked.
-     *
-     * <p> If a whole message is received, then the corresponding method
-     * ({@code onText} or {@code onBinary}) will be invoked with {@link
-     * WebSocket.MessagePart#WHOLE WHOLE} marker. Otherwise the method will be
-     * invoked with {@link WebSocket.MessagePart#FIRST FIRST}, zero or more
-     * times with {@link WebSocket.MessagePart#PART PART} and, finally, with
-     * {@link WebSocket.MessagePart#LAST LAST} markers.
+     * <p> A {@code WebSocket} invokes methods on its listener when it receives
+     * messages or encounters events. The invoking {@code WebSocket} is passed
+     * as an argument to {@code Listener}'s methods. A {@code WebSocket} invokes
+     * methods on its listener in a thread-safe manner.
      *
-     * If any of the methods above throws an exception, {@code onError} is then
-     * invoked with the same {@code WebSocket} and this exception. Exceptions
-     * thrown from {@code onError} or {@code onClose} are ignored.
-     *
-     * <p> When the method returns, the message is deemed received (in
-     * particular, if contained in a {@code ByteBuffer buffer}, the data is
-     * deemed received completely regardless of the result {@code
-     * buffer.hasRemaining()} upon the method's return. After this further
-     * messages may be received.
+     * <p> Unless otherwise stated if a listener's method throws an exception or
+     * a {@code CompletionStage} returned from a method completes exceptionally,
+     * the {@code WebSocket} will invoke {@code onError} with this exception.
      *
-     * <p> These invocations begin asynchronous processing which might not end
-     * with the invocation. To provide coordination, methods of {@code Listener}
-     * return a {@link CompletionStage CompletionStage}.
-     * The {@code CompletionStage} signals the {@code WebSocket} that the
-     * processing of a message has ended. For convenience, methods may return
-     * {@code null}, which (by convention) means the same as returning an
-     * already completed (normally) {@code CompletionStage}.
-     * If the returned {@code CompletionStage} completes exceptionally, then
-     * {@link #onError(WebSocket, Throwable) onError} will be invoked with the
-     * same {@code WebSocket} and this exception.
-     *
-     * <p> Control of the message passes to the {@code Listener} with the
-     * invocation of the method. Control of the message returns to the {@code
-     * WebSocket} at the earliest of, either returning {@code null} from the
-     * method, or the completion of the {@code CompletionStage} returned from
-     * the method. The {@code WebSocket} does not access the message while it's
-     * not in its control. The {@code Listener} must not access the message
-     * after its control has been returned to the {@code WebSocket}.
-     *
-     * <p> A {@code WebSocket} implementation never invokes {@code Listener}'s
-     * methods with {@code null}s as their arguments.
+     * <p> If a listener's method returns {@code null} rather than a
+     * {@code CompletionStage}, {@code WebSocket} will behave as if the listener
+     * returned a {@code CompletionStage} that has already completed normally.
      *
      * @since 9
      */
     interface Listener {
 
         /**
-         * Notifies the {@code Listener} that it is connected to the provided
-         * {@code WebSocket}.
-         *
-         * <p> The {@code onOpen} method does not correspond to any message from
-         * the WebSocket Protocol. It is a synthetic event and the first {@code
-         * Listener}'s method to be invoked.
+         * A {@code WebSocket} has been connected.
          *
-         * <p> This method is usually used to make an initial {@linkplain
-         * WebSocket#request(long) request} for messages.
-         *
-         * <p> If an exception is thrown from this method then {@link
-         * #onError(WebSocket, Throwable) onError} will be invoked with the same
-         * {@code WebSocket} and this exception.
+         * <p> This is the first invocation and it is made at most once. This
+         * method is typically used to make an initial request for messages.
          *
          * @implSpec The default implementation of this method behaves as if:
          *
@@ -298,24 +241,24 @@
          * }</pre>
          *
          * @param webSocket
-         *         the WebSocket
+         *         the WebSocket which has been connected
          */
         default void onOpen(WebSocket webSocket) { webSocket.request(1); }
 
         /**
-         * Receives a Text message.
-         *
-         * <p> The {@code onText} method is invoked zero or more times between
-         * {@code onOpen} and ({@code onClose} or {@code onError}).
+         * A Text message has been received.
          *
-         * <p> This message may be a partial UTF-16 sequence. However, the
-         * concatenation of all messages through the last will be a whole UTF-16
-         * sequence.
+         * <p> If a whole message has been received, this method will be invoked
+         * with {@code MessagePart.WHOLE} marker. Otherwise, it will be invoked
+         * with {@code FIRST}, possibly a number of times with {@code PART} and,
+         * finally, with {@code LAST} markers. If this message is partial, it
+         * may be an incomplete UTF-16 sequence. However, the concatenation of
+         * all messages through the last will be a complete UTF-16 sequence.
          *
-         * <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 the same
-         * {@code WebSocket} and this exception.
+         * <p> Return a {@code CompletionStage} which will be used by the
+         * {@code WebSocket} as a signal it may reclaim the
+         * {@code CharSequence}. Do not access the {@code CharSequence} after
+         * this {@ode CompletionStage} has completed.
          *
          * @implSpec The default implementation of this method behaves as if:
          *
@@ -324,18 +267,19 @@
          *     return null;
          * }</pre>
          *
-         * @implNote This implementation passes only complete UTF-16 sequences
-         * to the {@code onText} method.
+         * @implNote This method is always invoked with character sequences
+         * which are complete UTF-16 sequences.
          *
          * @param webSocket
-         *         the WebSocket
+         *         the WebSocket on which the message has been received
          * @param message
          *         the message
          * @param part
          *         the part
          *
-         * @return a {@code CompletionStage} which completes when the message
-         * processing is done; or {@code null} if already done
+         * @return a {@code CompletionStage} which completes when the
+         * {@code CharSequence} may be reclaimed; or {@code null} if it may be
+         * reclaimed immediately
          */
         default CompletionStage<?> onText(WebSocket webSocket,
                                           CharSequence message,
@@ -345,15 +289,20 @@
         }
 
         /**
-         * Receives a Binary message.
+         * A Binary message has been received.
          *
-         * <p> The {@code onBinary} method is invoked zero or more times
-         * between {@code onOpen} and ({@code onClose} or {@code onError}).
+         * <p> If a whole message has been received, this method will be invoked
+         * with {@code MessagePart.WHOLE} marker. Otherwise, it will be invoked
+         * with {@code FIRST}, possibly a number of times with {@code PART} and,
+         * finally, with {@code LAST} markers.
          *
-         * <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 the same
-         * {@code WebSocket} and this exception.
+         * <p> This message consists of bytes from the buffer's position to
+         * its limit.
+         *
+         * <p> Return a {@code CompletionStage} which will be used by the
+         * {@code WebSocket} as a signal it may reclaim the
+         * {@code ByteBuffer}. Do not access the {@code ByteBuffer} after
+         * this {@ode CompletionStage} has completed.
          *
          * @implSpec The default implementation of this method behaves as if:
          *
@@ -363,14 +312,15 @@
          * }</pre>
          *
          * @param webSocket
-         *         the WebSocket
+         *         the WebSocket on which the message has been received
          * @param message
          *         the message
          * @param part
          *         the part
          *
-         * @return a {@code CompletionStage} which completes when the message
-         * processing is done; or {@code null} if already done
+         * @return a {@code CompletionStage} which completes when the
+         * {@code ByteBuffer} may be reclaimed; or {@code null} if it may be
+         * reclaimed immediately
          */
         default CompletionStage<?> onBinary(WebSocket webSocket,
                                             ByteBuffer message,
@@ -380,34 +330,15 @@
         }
 
         /**
-         * Receives a Ping message.
+         * A Ping message has been received.
          *
-         * <p> A Ping message may be sent or received by either client or
-         * server. It may serve either as a keepalive or as a means to verify
-         * that the remote endpoint is still responsive.
+         * <p> The message consists of not more than {@code 125} bytes from
+         * the buffer's position to its limit.
          *
-         * <p> The {@code WebSocket} handles Ping messages by replying with
-         * appropriate Pong messages using a strategy of its choice, but within
-         * the boundaries of the WebSocket Protocol. The {@code WebSocket} may
-         * invoke {@code onPing} after handling a Ping message, before doing so
-         * or in parallel with it. In other words no particular ordering is
-         * guaranteed. If an error occurs while implementation handles this Ping
-         * message, then {@code onError} will be invoked with this error. For
-         * more details on handling Ping messages see RFC 6455 sections
-         * <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">5.5.2. Ping</a>
-         * and
-         * <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">5.5.3. Pong</a>.
-         *
-         * <p> The message will consist of not more than {@code 125} bytes:
-         * {@code message.remaining() <= 125}.
-         *
-         * <p> The {@code onPing} 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.
+         * <p> Return a {@code CompletionStage} which will be used by the
+         * {@code WebSocket} as a signal it may reclaim the
+         * {@code ByteBuffer}. Do not access the {@code ByteBuffer} after
+         * this {@ode CompletionStage} has completed.
          *
          * @implSpec The default implementation of this method behaves as if:
          *
@@ -417,12 +348,13 @@
          * }</pre>
          *
          * @param webSocket
-         *         the WebSocket
+         *         the WebSocket on which the message has been received
          * @param message
          *         the message
          *
-         * @return a {@code CompletionStage} which completes when the message
-         * processing is done; or {@code null} if already done
+         * @return a {@code CompletionStage} which completes when the
+         * {@code ByteBuffer} may be reclaimed; or {@code null} if it may be
+         * reclaimed immediately
          */
         default CompletionStage<?> onPing(WebSocket webSocket,
                                           ByteBuffer message) {
@@ -431,22 +363,15 @@
         }
 
         /**
-         * Receives a Pong message.
+         * A Pong message has been received.
          *
-         * <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 consists of not more than {@code 125} bytes from
+         * the buffer's position to its limit.
          *
-         * <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.
+         * <p> Return a {@code CompletionStage} which will be used by the
+         * {@code WebSocket} as a signal it may reclaim the
+         * {@code ByteBuffer}. Do not access the {@code ByteBuffer} after
+         * this {@ode CompletionStage} has completed.
          *
          * @implSpec The default implementation of this method behaves as if:
          *
@@ -456,12 +381,13 @@
          * }</pre>
          *
          * @param webSocket
-         *         the WebSocket
+         *         the WebSocket on which the message has been received
          * @param message
          *         the message
          *
-         * @return a {@code CompletionStage} which completes when the message
-         * processing is done; or {@code null} if already done
+         * @return a {@code CompletionStage} which completes when the
+         * {@code ByteBuffer} may be reclaimed; or {@code null} if it may be
+         * reclaimed immediately
          */
         default CompletionStage<?> onPong(WebSocket webSocket,
                                           ByteBuffer message) {
@@ -470,52 +396,39 @@
         }
 
         /**
-         * Receives a Close message.
+         * A Close message has been received.
+         *
+         * <p> This is the last invocation from the {@code WebSocket}. By the
+         * time this invocation begins the {@code WebSocket}'s input will have
+         * been closed. Be prepared to receive this invocation at any time after
+         * {@code onOpen} regardless of whether or not any messages have been
+         * requested from the {@code WebSocket}.
          *
          * <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} completes the
-         * closing handshake by replying with an appropriate Close message.
-         *
-         * <p> This implementation replies with a Close message that has the
-         * same code this message has and an empty reason.
+         * closing. The status code is an integer from the range
+         * {@code 1000 <= code <= 65535}. The {@code reason} is a string which
+         * has an UTF-8 representation not longer than {@code 123} bytes.
          *
-         * <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> Return a {@code CompletionStage} that will be used by the
+         * {@code WebSocket} as a signal that it may close the output. The
+         * {@code WebSocket} will close the output at the earliest of completion
+         * of the returned {@code CompletionStage} or invoking a
+         * {@link WebSocket#sendClose(int, String) sendClose} method.
          *
-         * <p> The {@code WebSocket} will close at the earliest of completion of
-         * the returned {@code CompletionStage} or sending a Close message. In
-         * particular, if a Close message has been {@linkplain WebSocket#sendClose
-         * sent} before, then this invocation completes the closing handshake
-         * and by the time this method is invoked, the {@code WebSocket} will
-         * have been closed.
-         *
-         * @implSpec The default implementation of this method behaves as if:
-         *
-         * <pre>{@code
-         *     return null;
-         * }</pre>
+         * <p> If an exception is thrown from this method or a
+         * {@code CompletionStage} returned from it completes exceptionally,
+         * the resulting behaviour is undefined.
          *
          * @param webSocket
-         *         the WebSocket
+         *         the WebSocket on which the message has been received
          * @param statusCode
          *         the status code
          * @param reason
          *         the reason
          *
-         * @return a {@code CompletionStage} which completes when the {@code
-         * WebSocket} can be closed; or {@code null} if it can be closed immediately
-         *
-         * @see #NORMAL_CLOSURE
+         * @return a {@code CompletionStage} which completes when the
+         * {@code WebSocket} may be closed; or {@code null} if it may be
+         * closed immediately
          */
         default CompletionStage<?> onClose(WebSocket webSocket,
                                            int statusCode,
@@ -524,31 +437,19 @@
         }
 
         /**
-         * Notifies an I/O or protocol error has occurred.
-         *
-         * <p> The {@code onError} method does not correspond to any message
-         * from the WebSocket Protocol. It is a synthetic event and the last
-         * {@code Listener}'s method to be invoked. It is invoked at most once
-         * but after {@code onOpen}. If an exception is thrown from this method,
-         * it is ignored.
+         * An unrecoverable error has occurred.
          *
-         * <p> Note that the WebSocket Protocol requires <i>some</i> errors
-         * occur in the incoming destination must be fatal to the connection. In
-         * such cases the implementation takes care of <i>Failing the WebSocket
-         * Connection</i>: by the time {@code onError} is invoked, the {@code
-         * WebSocket} will have been closed. Any outstanding or subsequent send
-         * operation will complete exceptionally with an {@code IOException}.
-         * For more details on Failing the WebSocket Connection see RFC 6455
-         * section <a href="https://tools.ietf.org/html/rfc6455#section-7.1.7">7.1.7. Fail the WebSocket Connection</a>.
+         * <p> This is the last invocation from the {@code WebSocket}. By the
+         * time this invocation begins both {@code WebSocket}'s input and output
+         * will have been closed. Be prepared to receive this invocation at any
+         * time after {@code onOpen} regardless of whether or not any messages
+         * have been requested from the {@code WebSocket}.
          *
-         * @apiNote Errors associated with sending messages are reported to the
-         * {@code CompletableFuture}s {@code sendX} methods return, rather than
-         * to this this method.
-         *
-         * @implSpec The default implementation of this method does nothing.
+         * <p> If an exception is thrown from this method, resulting behavior is
+         * undefined.
          *
          * @param webSocket
-         *         the WebSocket
+         *         the WebSocket on which the error has occurred
          * @param error
          *         the error
          */
@@ -556,29 +457,26 @@
     }
 
     /**
-     * A marker used by {@link WebSocket.Listener} in cases where a partial
-     * message may be received.
+     * A marker used by {@link WebSocket.Listener} for identifying partial
+     * messages.
      * {@Incubating}
      *
-     * @see Listener#onText(WebSocket, CharSequence, MessagePart)
-     * @see Listener#onBinary(WebSocket, ByteBuffer, MessagePart)
-     *
      * @since 9
      */
     enum MessagePart {
 
         /**
-         * The first part of a message in a sequence.
+         * The first part of a message.
          */
         FIRST,
 
         /**
-         * A middle part of a message in a sequence.
+         * A middle part of a message.
          */
         PART,
 
         /**
-         * The last part of a message in a sequence.
+         * The last part of a message.
          */
         LAST,
 
@@ -591,31 +489,28 @@
     /**
      * Sends a Text message with characters from the given {@code CharSequence}.
      *
-     * <p> Returns a {@code CompletableFuture<WebSocket>} which completes
-     * normally when the message has been sent or completes exceptionally if an
-     * error occurs.
+     * <p> To send a Text message invoke this method only after the previous
+     * Text or Binary message has been sent. The character sequence must not be
+     * modified until the {@code CompletableFuture} returned from this method
+     * has completed.
      *
-     * <p> The {@code CharSequence} must not be modified until the returned
-     * {@code CompletableFuture} completes (either normally or exceptionally).
-     *
-     * <p> The returned {@code CompletableFuture} can complete exceptionally
-     * with:
+     * <p> A {@code CompletableFuture} returned from this method can
+     * complete exceptionally with:
      * <ul>
      * <li> {@link IllegalArgumentException} -
      *          if {@code message} is a malformed UTF-16 sequence
      * <li> {@link IllegalStateException} -
-     *          if the {@code WebSocket} is closed;
-     *          or if a Close message has been sent;
-     *          or if there is an outstanding send operation;
-     *          or if a previous Binary message was sent with {@code isLast == false}
+     *          if {@code sendClose} has been invoked
+     *          or if the previous message has not been sent yet
+     *          or if a previous Binary message was sent with
+     *          {@code isLast == false}
      * <li> {@link IOException} -
-     *          if an I/O error occurs during this operation;
-     *          or if the {@code WebSocket} has been closed due to an error;
+     *          if an I/O error occurs
      * </ul>
      *
-     * @implNote This implementation does not accept partial UTF-16 sequences.
-     * In case such a sequence is passed, a returned {@code CompletableFuture}
-     * completes exceptionally with {@code IOException}.
+     * @implNote If a partial UTF-16 sequence is passed to this method, a
+     * {@code CompletableFuture} returned will complete exceptionally with
+     * {@code IOException}.
      *
      * @param message
      *         the message
@@ -630,21 +525,22 @@
     /**
      * Sends a Binary message with bytes from the given {@code ByteBuffer}.
      *
-     * <p> Returns a {@code CompletableFuture<WebSocket>} which completes
-     * normally when the message has been sent or completes exceptionally if an
-     * error occurs.
+     * <p> To send a Binary message invoke this method only after the previous
+     * Text or Binary message has been sent. The message consists of bytes from
+     * the buffer's position to its limit. Upon normal completion of a
+     * {@code CompletableFuture} returned from this method the buffer will have
+     * no remaining bytes. The buffer must not be accessed until that.
      *
-     * <p> The returned {@code CompletableFuture} can complete exceptionally
-     * with:
+     * <p> The {@code CompletableFuture} returned from this method can
+     * complete exceptionally with:
      * <ul>
      * <li> {@link IllegalStateException} -
-     *          if the {@code WebSocket} is closed;
-     *          or if a Close message has been sent;
-     *          or if there is an outstanding send operation;
-     *          or if a previous Text message was sent with {@code isLast == false}
+     *          if {@code sendClose} has been invoked
+     *          or if the previous message has not been sent yet
+     *          or if a previous Text message was sent with
+     *              {@code isLast == false}
      * <li> {@link IOException} -
-     *          if an I/O error occurs during this operation;
-     *          or if the {@code WebSocket} has been closed due to an error
+     *          if an I/O error occurs
      * </ul>
      *
      * @param message
@@ -658,31 +554,22 @@
     CompletableFuture<WebSocket> sendBinary(ByteBuffer message, boolean isLast);
 
     /**
-     * Sends a Ping message with bytes from the given ByteBuffer.
-     *
-     * <p> Returns a {@code CompletableFuture<WebSocket>} which completes
-     * normally when the message has been sent or completes exceptionally if an
-     * error occurs.
+     * Sends a Ping message with bytes from the given {@code ByteBuffer}.
      *
-     * <p> A Ping message may be sent or received by either client or server.
-     * It may serve either as a keepalive or as a means to verify that the
-     * remote endpoint is still responsive.
+     * <p> The message consists of not more than {@code 125} bytes from the
+     * buffer's position to its limit. Upon normal completion of a
+     * {@code CompletableFuture} returned from this method the buffer will
+     * have no remaining bytes. The buffer must not be accessed until that.
      *
-     * <p> The message must consist of not more than {@code 125} bytes: {@code
-     * message.remaining() <= 125}.
-     *
-     * <p> The returned {@code CompletableFuture} can complete exceptionally
-     * with:
+     * <p> The {@code CompletableFuture} returned from this method can
+     * complete exceptionally with:
      * <ul>
      * <li> {@link IllegalArgumentException} -
-     *          if {@code message.remaining() > 125}
+     *          if the message is too long
      * <li> {@link IllegalStateException} -
-     *          if the {@code WebSocket} is closed;
-     *          or if a Close message has been sent;
-     *          or if there is an outstanding send operation
+     *          if {@code sendClose} has been invoked
      * <li> {@link IOException} -
-     *          if an I/O error occurs during this operation;
-     *          or if the {@code WebSocket} has been closed due to an error
+     *          if an I/O error occurs
      * </ul>
      *
      * @param message
@@ -693,31 +580,22 @@
     CompletableFuture<WebSocket> sendPing(ByteBuffer message);
 
     /**
-     * Sends a Pong message with bytes from the given ByteBuffer.
-     *
-     * <p> Returns a {@code CompletableFuture<WebSocket>} which completes
-     * normally when the message has been sent or completes exceptionally if an
-     * error occurs.
+     * Sends a Pong message with bytes from the given {@code ByteBuffer}.
      *
-     * <p> A Pong message may be unsolicited or may be sent in response to a
-     * previously received Ping. In latter case the contents of the Pong must be
-     * identical to the originating Ping.
+     * <p> The message consists of not more than {@code 125} bytes from the
+     * buffer's position to its limit. Upon normal completion of a
+     * {@code CompletableFuture} returned from this method the buffer will
+     * have no remaining bytes. The buffer must not be accessed until that.
      *
-     * <p> The message must consist of not more than {@code 125} bytes: {@code
-     * message.remaining() <= 125}.
-     *
-     * <p> The returned {@code CompletableFuture} can complete exceptionally
-     * with:
+     * <p> The {@code CompletableFuture} returned from this method can
+     * complete exceptionally with:
      * <ul>
      * <li> {@link IllegalArgumentException} -
-     *          if {@code message.remaining() > 125}
+     *          if the message is too long
      * <li> {@link IllegalStateException} -
-     *          if the {@code WebSocket} is closed;
-     *          or if a Close message has been sent;
-     *          or if there is an outstanding send operation
+     *          if {@code sendClose} has been invoked
      * <li> {@link IOException} -
-     *          if an I/O error occurs during this operation;
-     *          or if the {@code WebSocket} has been closed due to an error
+     *          if an I/O error occurs
      * </ul>
      *
      * @param message
@@ -728,49 +606,38 @@
     CompletableFuture<WebSocket> sendPong(ByteBuffer message);
 
     /**
-     * Sends a Close message with the given status code and the reason.
-     *
-     * <p> When this method has been invoked, no further messages can be sent.
+     * Sends a Close message with the given status code and the reason,
+     * initiating an orderly closure.
      *
-     * <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. Regardless of an implementation,
-     * <code>{@value jdk.incubator.http.WebSocket#NORMAL_CLOSURE}</code>
-     * 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 always illegal codes.
+     * <p> By the time this method returns the output will have been closed.
      *
-     * <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>.
+     * <p> The {@code statusCode} is an integer from the range
+     * {@code 1000 <= code <= 4999}. Status codes {@code 1002}, {@code 1003},
+     * {@code 1006}, {@code 1007}, {@code 1009}, {@code 1010}, {@code 1012},
+     * {@code 1013} and {@code 1015} are illegal. Behaviour in respect to other
+     * status codes is implementation-specific. The {@code reason} is a string
+     * that has an UTF-8 representation not longer than {@code 123} bytes.
      *
-     * <p> The method returns a {@code CompletableFuture<WebSocket>} which
-     * completes normally when the message has been sent or completes
-     * exceptionally if an error occurs.
+     * <p> Use the provided integer constant {@link #NORMAL_CLOSURE} as a status
+     * code and an empty string as a reason in a typical case.
      *
-     * <p> The returned {@code CompletableFuture} can complete exceptionally
-     * with:
+     * <p> A {@code CompletableFuture} returned from this method can
+     * complete exceptionally with:
      * <ul>
      * <li> {@link IllegalArgumentException} -
-     *          if the {@code statusCode} has an illegal value;
-     *          or if {@code reason} doesn't have an UTF-8 representation of
-     *          length {@code <= 123}
+     *          if {@code statusCode} or {@code reason} are illegal
      * <li> {@link IOException} -
-     *          if an I/O error occurs during this operation;
-     *          or the {@code WebSocket} has been closed due to an error
+     *          if an I/O error occurs
      * </ul>
      *
-     * <p> If this method has already been invoked or the {@code WebSocket} is
-     * closed, then subsequent invocations of this method have no effect and the
-     * returned {@code CompletableFuture} completes normally.
-     *
-     * <p> If a Close message has been {@linkplain Listener#onClose(WebSocket,
-     * int, String) received} before, then this invocation completes the closing
-     * handshake and by the time the returned {@code CompletableFuture}
-     * completes, the {@code WebSocket} will have been closed.
+     * @implSpec An endpoint sending a Close message might not receive a
+     * complementing Close message in a timely manner for a variety of reasons.
+     * The {@code WebSocket} implementation is responsible for providing a
+     * closure mechanism that guarantees that once {@code sendClose} method has
+     * been invoked the {@code WebSocket} will close regardless of whether or
+     * not a Close frame has been received and without further intervention from
+     * the user of this API. Method {@code sendClose} is designed to be,
+     * possibly, the last call from the user of this API.
      *
      * @param statusCode
      *         the status code
@@ -782,71 +649,65 @@
     CompletableFuture<WebSocket> sendClose(int statusCode, String reason);
 
     /**
-     * Allows {@code n} more messages to be received by the {@link Listener
-     * Listener}.
-     *
-     * <p> The actual number of received messages might be fewer if a Close
-     * message is received, the {@code WebSocket} closes or an error occurs.
-     *
-     * <p> A {@code WebSocket} that has just been built, hasn't requested
-     * anything yet. Usually the initial request for messages is made in {@link
-     * Listener#onOpen(jdk.incubator.http.WebSocket) Listener.onOpen}.
+     * Requests {@code n} more messages from this {@code WebSocket}.
      *
-     * <p> If the {@code WebSocket} is closed then invoking this method has no
-     * effect.
+     * <p> This {@code WebSocket} will invoke its listener's {@code onText},
+     * {@code onBinary}, {@code onPing}, {@code onPong} or {@code onClose}
+     * methods up to {@code n} more times.
      *
-     * @implNote This implementation does not distinguish between partial and
-     * whole messages, because it's not known beforehand how a message will be
-     * received.
-     *
-     * <p> If a server sends more messages than requested, this implementation
-     * queues up these messages on the TCP connection and may eventually force
-     * the sender to stop sending through TCP flow control.
+     * <p> This method may be invoked at any time.
      *
      * @param n
-     *         the number of messages
+     *         the number of messages requested
      *
      * @throws IllegalArgumentException
-     *         if {@code n < 0}
+     *         if {@code n <= 0}
      */
     void request(long n);
 
     /**
-     * Returns a {@linkplain Builder#subprotocols(String, String...) subprotocol}
-     * which has been chosen for this {@code WebSocket}.
+     * Returns the subprotocol for this {@code WebSocket}.
      *
-     * @return a subprotocol, or an empty {@code String} if there is none
+     * <p> This method may be invoked at any time.
+     *
+     * @return the subprotocol for this {@code WebSocket}, or an empty
+     *         {@code String} if there's no subprotocol
      */
     String getSubprotocol();
 
     /**
-     * Tells whether the {@code WebSocket} is closed.
-     *
-     * <p> When a {@code WebSocket} is closed no further messages can be sent or
-     * received.
+     * Tells whether or not this {@code WebSocket} is permanently closed
+     * for sending messages.
      *
-     * @return {@code true} if the {@code WebSocket} is closed,
-     *         {@code false} otherwise
+     * <p> If this method returns {@code true}, subsequent invocations will also
+     * return {@code true}. This method may be invoked at any time.
+     *
+     * @return {@code true} if closed, {@code false} otherwise
      */
-    boolean isClosed();
+    boolean isOutputClosed();
 
     /**
-     * Closes the {@code WebSocket} abruptly.
+     * Tells whether or not this {@code WebSocket} is permanently closed
+     * for receiving messages.
      *
-     * <p> This method may be invoked at any time. This method closes the
-     * underlying TCP connection and puts the {@code WebSocket} into a closed
-     * state.
+     * <p> If this method returns {@code true}, subsequent invocations will also
+     * return {@code true}. This method may be invoked at any time.
      *
-     * <p> As the result {@link Listener#onClose(WebSocket, int, String)
-     * Listener.onClose} will be invoked unless either {@code onClose} or {@link
-     * Listener#onError(WebSocket, Throwable) onError} has been invoked before.
-     * In which case no additional invocation will happen.
+     * @return {@code true} if closed, {@code false} otherwise
+     */
+    boolean isInputClosed();
+
+    /**
+     * Closes this {@code WebSocket} abruptly.
      *
-     * <p> If the {@code WebSocket} is already closed then invoking this method
-     * has no effect.
+     * <p> By the time this method returns both input and output will have been
+     * closed. This method may be invoked at any time. Subsequent invocations
+     * will have no effect.
      *
-     * @throws IOException
-     *         if an I/O error occurs
+     * @apiNote Depending on its implementation, the state (for example, whether
+     * or not a message is being transferred at the moment) and possible errors
+     * while releasing associated resources, this {@code WebSocket} may invoke
+     * its listener's {@code onError}.
      */
-    void abort() throws IOException;
+    void abort();
 }
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/Receiver.java	Mon Nov 20 18:36:57 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/Receiver.java	Tue Nov 21 12:27:45 2017 +0300
@@ -95,8 +95,8 @@
     }
 
     void request(long n) {
-        if (n < 0L) {
-            throw new IllegalArgumentException("Negative: " + n);
+        if (n <= 0L) {
+            throw new IllegalArgumentException("Non-positive request: " + n);
         }
         demand.accumulateAndGet(n, (p, i) -> p + i < 0 ? Long.MAX_VALUE : p + i);
         pushScheduler.runOrSchedule();
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketImpl.java	Mon Nov 20 18:36:57 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketImpl.java	Tue Nov 21 12:27:45 2017 +0300
@@ -40,6 +40,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -62,7 +64,6 @@
 import static java.lang.String.format;
 import static java.util.Objects.requireNonNull;
 import static java.util.concurrent.CompletableFuture.failedFuture;
-import static java.util.stream.Collectors.joining;
 import static jdk.incubator.http.internal.common.Pair.pair;
 import static jdk.incubator.http.internal.common.Utils.permissionForProxy;
 import static jdk.incubator.http.internal.websocket.StatusCodes.CLOSED_ABNORMALLY;
@@ -79,6 +80,9 @@
     private final RawChannel channel;
     private final Listener listener;
 
+    private volatile boolean intputClosed;
+    private volatile boolean outputClosed;
+
     /*
      * Whether or not Listener.onClose or Listener.onError has been already
      * invoked. We keep track of this since only one of these methods is invoked
@@ -278,6 +282,7 @@
      * Processes a Close event that came from the channel. Invoked at most once.
      */
     private void processClose(int statusCode, String reason) {
+        intputClosed = true;
         receiver.close();
         try {
             channel.shutdownInput();
@@ -348,17 +353,18 @@
 
     @Override
     public CompletableFuture<WebSocket> sendPing(ByteBuffer message) {
-        return enqueueExclusively(new Ping(message));
+        return enqueue(new Ping(message));
     }
 
     @Override
     public CompletableFuture<WebSocket> sendPong(ByteBuffer message) {
-        return enqueueExclusively(new Pong(message));
+        return enqueue(new Pong(message));
     }
 
     @Override
     public CompletableFuture<WebSocket> sendClose(int statusCode,
                                                   String reason) {
+        outputClosed = true;
         if (!isLegalToSendFromClient(statusCode)) {
             return failedFuture(
                     new IllegalArgumentException("statusCode: " + statusCode));
@@ -373,23 +379,28 @@
     }
 
     /*
-     * Sends a Close message with the given contents and then shuts down the
-     * channel for writing since no more messages are expected to be sent after
-     * this. Invoked at most once.
+     * Sends a Close message and then shuts down the channel for writing since
+     * no more messages are expected to be sent after this.
      */
     private CompletableFuture<WebSocket> enqueueClose(Close m) {
-        return enqueue(m).whenComplete((r, error) -> {
-            try {
-                channel.shutdownOutput();
-            } catch (IOException e) {
-                Log.logError(e);
-            }
-            boolean alreadyCompleted = !closeSent.complete(null);
-            if (alreadyCompleted) {
-                // Shouldn't happen as this callback must run at most once
-                throw new InternalError();
-            }
-        });
+        return enqueue(m)
+                .orTimeout(60, TimeUnit.SECONDS)
+                .whenComplete((r, error) -> {
+                    if (error instanceof TimeoutException) {
+                        try {
+                            channel.close();
+                        } catch (IOException e) {
+                            Log.logError(e);
+                        }
+                    } else {
+                        try {
+                            channel.shutdownOutput();
+                        } catch (IOException e) {
+                            Log.logError(e);
+                        }
+                        closeSent.complete(null);
+                    }
+                });
     }
 
     /*
@@ -401,9 +412,6 @@
      */
     private CompletableFuture<WebSocket> enqueueExclusively(OutgoingMessage m)
     {
-        if (closed.get()) {
-            return failedFuture(new IllegalStateException("Closed"));
-        }
         if (!outstandingSend.compareAndSet(false, true)) {
             return failedFuture(new IllegalStateException("Outstanding send"));
         }
@@ -427,7 +435,7 @@
      */
     private class SendFirstTask implements SequentialScheduler.RestartableTask {
         @Override
-        public void run (DeferredCompleter taskCompleter){
+        public void run(DeferredCompleter taskCompleter) {
             Pair<OutgoingMessage, CompletableFuture<WebSocket>> p = queue.poll();
             if (p == null) {
                 taskCompleter.complete();
@@ -443,7 +451,6 @@
                     } else {
                         cf.completeExceptionally(e);
                     }
-                    sendScheduler.runOrSchedule();
                     taskCompleter.complete();
                 };
                 transmitter.send(message, h);
@@ -464,14 +471,22 @@
     }
 
     @Override
-    public boolean isClosed() {
-        return closed.get();
+    public boolean isOutputClosed() {
+        return outputClosed;
     }
 
     @Override
-    public void abort() throws IOException {
+    public boolean isInputClosed() {
+        return intputClosed;
+    }
+
+    @Override
+    public void abort() {
+        intputClosed = true;
+        outputClosed = true;
         try {
             channel.close();
+        } catch (IOException ignored) {
         } finally {
             closed.set(true);
             signalClose(CLOSED_ABNORMALLY, "");
@@ -565,12 +580,10 @@
 
             @Override
             public void onError(Exception error) {
-                // An signalError doesn't necessarily mean we must signalClose
-                // the WebSocket. However, if it's something the WebSocket
-                // Specification recognizes as a reason for "Failing the
-                // WebSocket Connection", then we must do so, but BEFORE
-                // notifying the Listener.
+                intputClosed = true;
+                outputClosed = true;
                 if (!(error instanceof FailWebSocketException)) {
+                    abort();
                     signalError(error);
                 } else {
                     Exception ex = (Exception) new ProtocolException().initCause(error);
--- a/test/jdk/java/net/httpclient/websocket/ConnectionHandover.java	Mon Nov 20 18:36:57 2017 +0000
+++ b/test/jdk/java/net/httpclient/websocket/ConnectionHandover.java	Tue Nov 21 12:27:45 2017 +0300
@@ -52,15 +52,11 @@
 
             WebSocket ws1 = webSocketBuilder
                     .buildAsync(uri, new WebSocket.Listener() { }).join();
-            try {
-                ws1.abort();
-            } catch (IOException ignored) { }
+            ws1.abort();
 
             WebSocket ws2 = webSocketBuilder
                     .buildAsync(uri, new WebSocket.Listener() { }).join(); // Exception here if the connection was pooled
-            try {
-                ws2.abort();
-            } catch (IOException ignored) { }
+            ws2.abort();
         }
     }
 }