jdk/src/java.httpclient/share/classes/java/net/http/WebSocket.java
changeset 37874 02589df0999a
child 38856 cc3a0d1e96e0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/WebSocket.java	Mon May 09 23:33:09 2016 +0100
@@ -0,0 +1,1288 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+/**
+ * A WebSocket client conforming to RFC 6455.
+ *
+ * <p> A {@code WebSocket} provides full-duplex communication over a TCP
+ * connection.
+ *
+ * <p> To create a {@code WebSocket} use a {@linkplain #newBuilder(URI, Listener)
+ * builder}. Once a {@code WebSocket} is obtained, 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, Optional,
+ * String) received}. Or to close abruptly, {@link #abort()} is called. Once
+ * closed it remains closed, cannot be reopened.
+ *
+ * <p> Messages of type {@code X} are sent through the {@code WebSocket.sendX}
+ * methods and received through {@link WebSocket.Listener}{@code .onX} methods
+ * asynchronously. Each of the methods begins the operation and returns a {@link
+ * CompletionStage} which completes when the operation has completed.
+ *
+ * <p> Messages are received only if {@linkplain #request(long) requested}.
+ *
+ * <p> One outstanding send operation is permitted: if another send operation is
+ * initiated before the previous one has completed, an {@link
+ * IllegalStateException IllegalStateException} will be thrown. When sending, a
+ * message should not be modified until the returned {@code CompletableFuture}
+ * completes (either normally or exceptionally).
+ *
+ * <p> Messages can be sent and received as a whole or in parts. A whole message
+ * is a sequence of one or more messages in which the last message is marked
+ * when it is sent or received.
+ *
+ * <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> All message exchange is run by the threads belonging to the {@linkplain
+ * HttpClient#executorService() executor service} of {@code WebSocket}'s {@link
+ * HttpClient}.
+ *
+ * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
+ * or method of this type will cause a {@link NullPointerException
+ * NullPointerException} to be thrown.
+ *
+ * @since 9
+ */
+public interface WebSocket {
+
+    /**
+     * Creates a builder of {@code WebSocket}s connected to the given URI and
+     * receiving events with the given {@code Listener}.
+     *
+     * <p> Equivalent to:
+     * <pre>{@code
+     *     WebSocket.newBuilder(uri, HttpClient.getDefault())
+     * }</pre>
+     *
+     * @param uri
+     *         the WebSocket URI as defined in the WebSocket Protocol
+     *         (with "ws" or "wss" scheme)
+     *
+     * @param listener
+     *         the listener
+     *
+     * @throws IllegalArgumentException
+     *         if the {@code uri} is not a WebSocket URI
+     * @throws SecurityException
+     *         if running under a security manager and the caller does
+     *         not have permission to access the
+     *         {@linkplain HttpClient#getDefault() default HttpClient}
+     *
+     * @return a builder
+     */
+    static Builder newBuilder(URI uri, Listener listener) {
+        return newBuilder(uri, HttpClient.getDefault(), listener);
+    }
+
+    /**
+     * Creates a builder of {@code WebSocket}s connected to the given URI and
+     * receiving events with the given {@code Listener}.
+     *
+     * <p> Providing a custom {@code client} allows for finer control over the
+     * opening handshake.
+     *
+     * <p> <b>Example</b>
+     * <pre>{@code
+     *     HttpClient client = HttpClient.create()
+     *             .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
+     *             .build();
+     *     ...
+     *     WebSocket.newBuilder(URI.create("ws://websocket.example.com"), client, listener)...
+     * }</pre>
+     *
+     * @param uri
+     *         the WebSocket URI as defined in the WebSocket Protocol
+     *         (with "ws" or "wss" scheme)
+     *
+     * @param client
+     *         the HttpClient
+     * @param listener
+     *         the listener
+     *
+     * @throws IllegalArgumentException
+     *         if the uri is not a WebSocket URI
+     *
+     * @return a builder
+     */
+    static Builder newBuilder(URI uri, HttpClient client, Listener listener) {
+        return new WSBuilder(uri, client, listener);
+    }
+
+    /**
+     * A builder for creating {@code WebSocket} instances.
+     *
+     * <p> To build a {@code WebSocket}, instantiate a builder, configure it
+     * as required by calling intermediate methods (the ones that return the
+     * builder itself), then finally call {@link #buildAsync()} to get a {@link
+     * CompletableFuture} with resulting {@code WebSocket}.
+     *
+     * <p> If an intermediate method has not been called, an appropriate
+     * default value (or behavior) will be used. Unless otherwise noted, a
+     * repeated call to an intermediate method overwrites the previous value (or
+     * overrides the previous behaviour), if no exception is thrown.
+     *
+     * <p> Instances of {@code Builder} may not be safe for use by multiple
+     * threads.
+     *
+     * @since 9
+     */
+    interface Builder {
+
+        /**
+         * Adds the given name-value pair to the list of additional headers for
+         * the opening handshake.
+         *
+         * <p> Headers defined in WebSocket Protocol are not allowed to be added.
+         *
+         * @param name
+         *         the header name
+         * @param value
+         *         the header value
+         *
+         * @return this builder
+         *
+         * @throws IllegalArgumentException
+         *         if the {@code name} is a WebSocket defined header name
+         */
+        Builder header(String name, String value);
+
+        /**
+         * Includes a request for the given subprotocols during the opening
+         * handshake.
+         *
+         * <p> Among the requested subprotocols at most one will be chosen by
+         * the server. When the {@code WebSocket} is connected, the subprotocol
+         * in use is available from {@link WebSocket#getSubprotocol}.
+         * Subprotocols may be specified in the order of preference.
+         *
+         * <p> Each of the given subprotocols must conform to the relevant
+         * rules defined in the WebSocket Protocol.
+         *
+         * @param mostPreferred
+         *         the most preferred subprotocol
+         * @param lesserPreferred
+         *         the lesser preferred subprotocols, with the least preferred
+         *         at the end
+         *
+         * @return this builder
+         *
+         * @throws IllegalArgumentException
+         *         if any of the WebSocket Protocol rules relevant to
+         *         subprotocols are violated
+         */
+        Builder subprotocols(String mostPreferred, String... lesserPreferred);
+
+        /**
+         * Sets a timeout for the opening handshake.
+         *
+         * <p> If the opening handshake is not finished within the specified
+         * timeout then {@link #buildAsync()} completes exceptionally with a
+         * {@code HttpTimeoutException}.
+         *
+         * <p> If the timeout is not specified then it's deemed infinite.
+         *
+         * @param timeout
+         *         the maximum time to wait
+         * @param unit
+         *         the time unit of the timeout argument
+         *
+         * @return this builder
+         *
+         * @throws IllegalArgumentException
+         *         if the {@code timeout} is negative
+         */
+        Builder connectTimeout(long timeout, TimeUnit unit);
+
+        /**
+         * Builds a {@code WebSocket}.
+         *
+         * <p> Returns immediately with a {@code CompletableFuture<WebSocket>}
+         * which completes with the {@code WebSocket} when it is connected, or
+         * completes exceptionally if an error occurs.
+         *
+         * <p> {@code CompletableFuture} may complete exceptionally with the
+         * following errors:
+         * <ul>
+         * <li> {@link IOException}
+         *          if an I/O error occurs
+         * <li> {@link InterruptedException}
+         *          if the operation was interrupted
+         * <li> {@link SecurityException}
+         *          if a security manager is set, and the caller does not
+         *          have a {@link java.net.URLPermission} for the WebSocket URI
+         * <li> {@link WebSocketHandshakeException}
+         *          if the opening handshake fails
+         * </ul>
+         *
+         * @return a {@code CompletableFuture} of {@code WebSocket}
+         */
+        CompletableFuture<WebSocket> buildAsync();
+    }
+
+    /**
+     * A listener for events and messages on a {@code WebSocket}.
+     *
+     * <p> Each method below corresponds to a type of event.
+     * <ul>
+     * <li> {@link #onOpen onOpen} <br>
+     * This method is always the first to be invoked.
+     * <li> {@link #onText(WebSocket, WebSocket.Text, 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, Optional, String) onClose}, {@link
+     * #onError(WebSocket, Throwable) onError} <br>
+     * Only one of these methods is invoked, and that method is invoked last and
+     * at most once.
+     * </ul>
+     *
+     * <pre><code>
+     *     onOpen (onText|onBinary|onPing|onPong)* (onClose|onError)?
+     * </code></pre>
+     *
+     * <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#FIRST PART} and, finally, with
+     * {@link WebSocket.MessagePart#LAST LAST} markers.
+     *
+     * <pre><code>
+     *     WHOLE|(FIRST PART* LAST)
+     * </code></pre>
+     *
+     * <p> All methods 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. If any of the methods above throws an exception, {@code onError}
+     * is then invoked with that exception. Exceptions thrown from {@code
+     * onError} or {@code onClose} are ignored.
+     *
+     * <p> When the method returns, the message is deemed received. After this
+     * another messages may be received.
+     *
+     * <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 means
+     * the same as returning an already completed {@code CompletionStage}. If
+     * the returned {@code CompletionStage} completes exceptionally, then {@link
+     * #onError(WebSocket, Throwable) onError} will be invoked with the
+     * 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> It is the responsibility of the listener to make additional
+     * {@linkplain WebSocket#request(long) message requests}, when ready, so
+     * that messages are received eventually.
+     *
+     * <p> Methods above are never invoked with {@code null}s as their
+     * arguments.
+     *
+     * @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. It is the first
+         * {@code Listener}'s method to be invoked. No other {@code Listener}'s
+         * methods are invoked before this one. The 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
+         * exception.
+         *
+         * @implSpec The default implementation {@linkplain WebSocket#request(long)
+         * requests one message}.
+         *
+         * @param webSocket
+         *         the WebSocket
+         */
+        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}).
+         *
+         * <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 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
+         * exception.
+         *
+         * @implSpec The default implementation {@linkplain WebSocket#request(long)
+         * requests one more message}.
+         *
+         * @param webSocket
+         *         the WebSocket
+         * @param message
+         *         the message
+         * @param part
+         *         the part
+         *
+         * @return a CompletionStage that completes when the message processing
+         * is done; or {@code null} if already done
+         */
+        default CompletionStage<?> onText(WebSocket webSocket,
+                                          Text message,
+                                          MessagePart part) {
+            webSocket.request(1);
+            return null;
+        }
+
+        /**
+         * Receives a Binary message.
+         *
+         * <p> The {@code onBinary} method is invoked zero or more times
+         * 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 {@linkplain WebSocket#request(long)
+         * requests one more message}.
+         *
+         * @param webSocket
+         *         the WebSocket
+         * @param message
+         *         the message
+         * @param part
+         *         the part
+         *
+         * @return a CompletionStage that completes when the message processing
+         * is done; or {@code null} if already done
+         */
+        default CompletionStage<?> onBinary(WebSocket webSocket,
+                                            ByteBuffer message,
+                                            MessagePart part) {
+            webSocket.request(1);
+            return null;
+        }
+
+        /**
+         * Receives a Ping message.
+         *
+         * <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 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.
+         *
+         * @implNote
+         *
+         * <p> Replies with a Pong message and requests one more message when
+         * the Pong has been sent.
+         *
+         * @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<?> onPing(WebSocket webSocket,
+                                          ByteBuffer message) {
+            return webSocket.sendPong(message).thenRun(() -> webSocket.request(1));
+        }
+
+        /**
+         * 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 {@linkplain WebSocket#request(long)
+         * requests one more message}.
+         *
+         * @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 may consist of a close code and a reason for
+         * closing. The reason will have a UTF-8 representation not longer than
+         * {@code 123} bytes. The reason may be useful for debugging or passing
+         * information relevant to the connection but is not necessarily human
+         * readable.
+         *
+         * <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.
+         *
+         * @implSpec The default implementation does nothing.
+         *
+         * @param webSocket
+         *         the WebSocket
+         * @param code
+         *         an {@code Optional} describing the close code, or
+         *         an empty {@code Optional} if the message doesn't contain it
+         * @param reason
+         *         the reason of close; can be empty
+         */
+        default void onClose(WebSocket webSocket, Optional<CloseCode> code,
+                             String reason) { }
+
+        /**
+         * Notifies an I/O or protocol error has occurred on the {@code
+         * WebSocket}.
+         *
+         * <p> The {@code onError} method does not correspond to any message
+         * from the WebSocket Protocol. It is a synthetic event. {@code onError}
+         * 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> The WebSocket Protocol requires some errors occurs in the
+         * incoming destination must be fatal to the connection. In such cases
+         * the implementation takes care of closing the {@code WebSocket}. By
+         * the time {@code onError} is invoked, no more messages can be sent on
+         * this {@code WebSocket}.
+         *
+         * @apiNote Errors associated with send operations ({@link
+         * WebSocket#sendText(CharSequence, boolean) sendText}, {@link
+         * #sendBinary(ByteBuffer, boolean) sendBinary}, {@link
+         * #sendPing(ByteBuffer) sendPing}, {@link #sendPong(ByteBuffer)
+         * sendPong} and {@link #sendClose(CloseCode, CharSequence) sendClose})
+         * are reported to the {@code CompletionStage} operations return.
+         *
+         * @implSpec The default implementation does nothing.
+         *
+         * @param webSocket
+         *         the WebSocket
+         * @param error
+         *         the error
+         */
+        default void onError(WebSocket webSocket, Throwable error) { }
+    }
+
+    /**
+     * A marker used by {@link WebSocket.Listener} for partial message
+     * receiving.
+     *
+     * @since 9
+     */
+    enum MessagePart {
+
+        /**
+         * The first part of a message in a sequence.
+         */
+        FIRST,
+
+        /**
+         * A middle part of a message in a sequence.
+         */
+        PART,
+
+        /**
+         * The last part of a message in a sequence.
+         */
+        LAST,
+
+        /**
+         * A whole message. The message consists of a single part.
+         */
+        WHOLE;
+
+        /**
+         * Tells whether a part of a message received with this marker is the
+         * last part.
+         *
+         * @return {@code true} if LAST or WHOLE, {@code false} otherwise
+         */
+        public boolean isLast() {
+            return this == LAST || this == WHOLE;
+        }
+    }
+
+    /**
+     * Sends a Text message with bytes from the given {@code ByteBuffer}.
+     *
+     * <p> Returns immediately with a {@code CompletableFuture<Void>} which
+     * completes normally when the message has been sent, or completes
+     * exceptionally if an error occurs.
+     *
+     * <p> This message may be a partial UTF-8 sequence. However, the
+     * concatenation of all messages through the last must be a whole UTF-8
+     * sequence.
+     *
+     * <p> The {@code ByteBuffer} should not be modified until the returned
+     * {@code CompletableFuture} completes (either normally or exceptionally).
+     *
+     * <p> The returned {@code CompletableFuture} can complete exceptionally
+     * with:
+     * <ul>
+     * <li> {@link IOException}
+     *          if an I/O error occurs during this operation; or the
+     *          {@code WebSocket} closes while this operation is in progress;
+     *          or the {@code message} is a malformed UTF-8 sequence
+     * </ul>
+     *
+     * @param message
+     *         the message
+     * @param isLast
+     *         {@code true} if this is the final part of the message,
+     *         {@code false} otherwise
+     *
+     * @return a CompletableFuture of Void
+     *
+     * @throws IllegalStateException
+     *         if the WebSocket is closed
+     * @throws IllegalStateException
+     *         if a Close message has been sent already
+     * @throws IllegalStateException
+     *         if there is an outstanding send operation
+     * @throws IllegalStateException
+     *         if a previous Binary message
+     *         was not sent with {@code isLast == true}
+     */
+    CompletableFuture<Void> sendText(ByteBuffer message, boolean isLast);
+
+    /**
+     * Sends a Text message with characters from the given {@code
+     * CharSequence}.
+     *
+     * <p> Returns immediately with a {@code CompletableFuture<Void>} which
+     * completes normally when the message has been sent, or completes
+     * exceptionally if an error occurs.
+     *
+     * <p> This message may be a partial UTF-16 sequence. However, the
+     * concatenation of all messages through the last must be a whole UTF-16
+     * sequence.
+     *
+     * <p> The {@code CharSequence} should not be modified until the returned
+     * {@code CompletableFuture} completes (either normally or exceptionally).
+     *
+     * <p> The returned {@code CompletableFuture} can complete exceptionally
+     * with:
+     * <ul>
+     * <li> {@link IOException}
+     *          if an I/O error occurs during this operation; or the
+     *          {@code WebSocket} closes while this operation is in progress;
+     *          or the {@code message} is a malformed UTF-16 sequence
+     * </ul>
+     *
+     * @param message
+     *         the message
+     * @param isLast
+     *         {@code true} if this is the final part of the message
+     *         {@code false} otherwise
+     *
+     * @return a CompletableFuture of Void
+     *
+     * @throws IllegalStateException
+     *         if the WebSocket is closed
+     * @throws IllegalStateException
+     *         if a Close message has been already sent
+     * @throws IllegalStateException
+     *         if there is an outstanding send operation
+     * @throws IllegalStateException
+     *         if a previous Binary message was not sent
+     *         with {@code isLast == true}
+     */
+    CompletableFuture<Void> sendText(CharSequence message, boolean isLast);
+
+    /**
+     * Sends a whole Text message with characters from the given {@code
+     * CharSequence}.
+     *
+     * <p> This is a convenience method. For the general case, use {@link
+     * #sendText(CharSequence, boolean)}.
+     *
+     * <p> Returns immediately with a {@code CompletableFuture<Void>} which
+     * completes normally when the message has been sent, or completes
+     * exceptionally if an error occurs.
+     *
+     * <p> The {@code CharSequence} should not be modified until the returned
+     * {@code CompletableFuture} completes (either normally or exceptionally).
+     *
+     * <p> The returned {@code CompletableFuture} can complete exceptionally
+     * with:
+     * <ul>
+     * <li> {@link IOException}
+     *          if an I/O error occurs during this operation; or the
+     *          {@code WebSocket} closes while this operation is in progress;
+     *          or the message is a malformed UTF-16 sequence
+     * </ul>
+     *
+     * @param message
+     *         the message
+     *
+     * @return a CompletableFuture of Void
+     *
+     * @throws IllegalStateException
+     *         if the WebSocket is closed
+     * @throws IllegalStateException
+     *         if a Close message has been already sent
+     * @throws IllegalStateException
+     *         if there is an outstanding send operation
+     * @throws IllegalStateException
+     *         if a previous Binary message was not sent
+     *         with {@code isLast == true}
+     */
+    default CompletableFuture<Void> sendText(CharSequence message) {
+        return sendText(message, true);
+    }
+
+    /**
+     * Sends a whole Text message with characters from {@code
+     * CharacterSequence}s provided by the given {@code Stream}.
+     *
+     * <p> This is a convenience method. For the general case use {@link
+     * #sendText(CharSequence, boolean)}.
+     *
+     * <p> Returns immediately with a {@code CompletableFuture<Void>} which
+     * completes normally when the message has been sent, or completes
+     * exceptionally if an error occurs.
+     *
+     * <p> Streamed character sequences should not be modified until the
+     * returned {@code CompletableFuture} completes (either normally or
+     * exceptionally).
+     *
+     * <p> The returned {@code CompletableFuture} can complete exceptionally
+     * with:
+     * <ul>
+     * <li> {@link IOException}
+     *          if an I/O error occurs during this operation; or the
+     *          {@code WebSocket} closes while this operation is in progress;
+     *          or the message is a malformed UTF-16 sequence
+     * </ul>
+     *
+     * @param message
+     *         the message
+     *
+     * @return a CompletableFuture of Void
+     *
+     * @throws IllegalStateException
+     *         if the WebSocket is closed
+     * @throws IllegalStateException
+     *         if a Close message has been already sent
+     * @throws IllegalStateException
+     *         if there is an outstanding send operation
+     * @throws IllegalStateException
+     *         if a previous Binary message was not sent
+     *         with {@code isLast == true}
+     */
+    CompletableFuture<Void> sendText(Stream<? extends CharSequence> message);
+
+    /**
+     * Sends a Binary message with bytes from the given {@code ByteBuffer}.
+     *
+     * <p> Returns immediately with a {@code CompletableFuture<Void>} which
+     * completes normally when the message has been sent, or completes
+     * exceptionally if an error occurs.
+     *
+     * <p> The returned {@code CompletableFuture} can complete exceptionally
+     * with:
+     * <ul>
+     * <li> {@link IOException}
+     *          if an I/O error occurs during this operation or the
+     *          {@code WebSocket} closes while this operation is in progress
+     * </ul>
+     *
+     * @param message
+     *         the message
+     * @param isLast
+     *         {@code true} if this is the final part of the message,
+     *         {@code false} otherwise
+     *
+     * @return a CompletableFuture of Void
+     *
+     * @throws IllegalStateException
+     *         if the WebSocket is closed
+     * @throws IllegalStateException
+     *         if a Close message has been already sent
+     * @throws IllegalStateException
+     *         if there is an outstanding send operation
+     * @throws IllegalStateException
+     *         if a previous Text message was not sent
+     *         with {@code isLast == true}
+     */
+    CompletableFuture<Void> sendBinary(ByteBuffer message, boolean isLast);
+
+    /**
+     * Sends a Binary message with bytes from the given {@code byte[]}.
+     *
+     * <p> Returns immediately with a {@code CompletableFuture<Void>} which
+     * completes normally when the message has been sent, or completes
+     * exceptionally if an error occurs.
+     *
+     * <p> The returned {@code CompletableFuture} can complete exceptionally
+     * with:
+     * <ul>
+     * <li> {@link IOException}
+     *          if an I/O error occurs during this operation or the
+     *          {@code WebSocket} closes while this operation is in progress
+     * </ul>
+     *
+     * @implSpec This is equivalent to:
+     * <pre>{@code
+     *     sendBinary(ByteBuffer.wrap(message), isLast)
+     * }</pre>
+     *
+     * @param message
+     *         the message
+     * @param isLast
+     *         {@code true} if this is the final part of the message,
+     *         {@code false} otherwise
+     *
+     * @return a CompletableFuture of Void
+     *
+     * @throws IllegalStateException
+     *         if the WebSocket is closed
+     * @throws IllegalStateException
+     *         if a Close message has been already sent
+     * @throws IllegalStateException
+     *         if there is an outstanding send operation
+     * @throws IllegalStateException
+     *         if a previous Text message was not sent
+     *         with {@code isLast == true}
+     */
+    default CompletableFuture<Void> sendBinary(byte[] message, boolean isLast) {
+        Objects.requireNonNull(message, "message");
+        return sendBinary(ByteBuffer.wrap(message), isLast);
+    }
+
+    /**
+     * Sends a Ping message.
+     *
+     * <p> Returns immediately with a {@code CompletableFuture<Void>} which
+     * completes normally when the message has been sent, or completes
+     * exceptionally if an error occurs.
+     *
+     * <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 must consist of not more than {@code 125} bytes: {@code
+     * message.remaining() <= 125}.
+     *
+     * <p> The returned {@code CompletableFuture} can complete exceptionally
+     * with:
+     * <ul>
+     * <li> {@link IOException}
+     *          if an I/O error occurs during this operation or the
+     *          {@code WebSocket} closes while this operation is in progress
+     * </ul>
+     *
+     * @param message
+     *         the message
+     *
+     * @return a CompletableFuture of Void
+     *
+     * @throws IllegalStateException
+     *         if the WebSocket is closed
+     * @throws IllegalStateException
+     *         if a Close message has been already sent
+     * @throws IllegalStateException
+     *         if there is an outstanding send operation
+     * @throws IllegalArgumentException
+     *         if {@code message.remaining() > 125}
+     */
+    CompletableFuture<Void> sendPing(ByteBuffer message);
+
+    /**
+     * Sends a Pong message.
+     *
+     * <p> Returns immediately with a {@code CompletableFuture<Void>} which
+     * completes normally when the message has been sent, or completes
+     * exceptionally if an error occurs.
+     *
+     * <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 is
+     * identical to the originating Ping.
+     *
+     * <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:
+     * <ul>
+     * <li> {@link IOException}
+     *          if an I/O error occurs during this operation or the
+     *          {@code WebSocket} closes while this operation is in progress
+     * </ul>
+     *
+     * @param message
+     *         the message
+     *
+     * @return a CompletableFuture of Void
+     *
+     * @throws IllegalStateException
+     *         if the WebSocket is closed
+     * @throws IllegalStateException
+     *         if a Close message has been already sent
+     * @throws IllegalStateException
+     *         if there is an outstanding send operation
+     * @throws IllegalArgumentException
+     *         if {@code message.remaining() > 125}
+     */
+    CompletableFuture<Void> sendPong(ByteBuffer message);
+
+    /**
+     * Sends a Close message with the given close code and the reason.
+     *
+     * <p> Returns immediately with a {@code CompletableFuture<Void>} which
+     * completes normally when the message has been sent, or completes
+     * exceptionally if an error occurs.
+     *
+     * <p> A Close message may consist of a close code and a reason for closing.
+     * The reason must have a valid UTF-8 representation not longer than {@code
+     * 123} bytes. The reason may be useful for debugging or passing information
+     * relevant to the connection but is not necessarily human readable.
+     *
+     * <p> The returned {@code CompletableFuture} can complete exceptionally
+     * with:
+     * <ul>
+     * <li> {@link IOException}
+     *          if an I/O error occurs during this operation or the
+     *          {@code WebSocket} closes while this operation is in progress
+     * </ul>
+     *
+     * @param code
+     *         the close code
+     * @param reason
+     *         the reason; can be empty
+     *
+     * @return a CompletableFuture of Void
+     *
+     * @throws IllegalStateException
+     *         if the WebSocket is closed
+     * @throws IllegalStateException
+     *         if a Close message has been already sent
+     * @throws IllegalStateException
+     *         if there is an outstanding send operation
+     * @throws IllegalArgumentException
+     *         if the {@code reason} doesn't have a valid UTF-8
+     *         representation not longer than {@code 123} bytes
+     */
+    CompletableFuture<Void> sendClose(CloseCode code, CharSequence reason);
+
+    /**
+     * Sends an empty Close message.
+     *
+     * <p> Returns immediately with a {@code CompletableFuture<Void>} which
+     * completes normally when the message has been sent, or completes
+     * exceptionally if an error occurs.
+     *
+     * <p> The returned {@code CompletableFuture} can complete exceptionally
+     * with:
+     * <ul>
+     * <li> {@link IOException}
+     *          if an I/O error occurs during this operation or the
+     *          {@code WebSocket} closes while this operation is in progress
+     * </ul>
+     *
+     * @return a CompletableFuture of Void
+     *
+     * @throws IllegalStateException
+     *         if the WebSocket is closed
+     * @throws IllegalStateException
+     *         if a Close message has been already sent
+     * @throws IllegalStateException
+     *         if there is an outstanding send operation
+     */
+    CompletableFuture<Void> sendClose();
+
+    /**
+     * Requests {@code n} more messages to be received by the {@link Listener
+     * Listener}.
+     *
+     * <p> The actual number might be fewer if either of the endpoints decide to
+     * close the connection before that or an error occurs.
+     *
+     * <p> A {@code WebSocket} that has just been created, hasn't requested
+     * anything yet. Usually the initial request for messages is done in {@link
+     * Listener#onOpen(java.net.http.WebSocket) Listener.onOpen}.
+     *
+     * If all requested messages have been received, and the server sends more,
+     * then these messages are queued.
+     *
+     * @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, the implementation
+     * queues up these messages on the TCP connection and may eventually force
+     * the sender to stop sending through TCP flow control.
+     *
+     * @param n
+     *         the number of messages
+     *
+     * @throws IllegalArgumentException
+     *         if {@code n < 0}
+     *
+     * @return resulting unfulfilled demand with this request taken into account
+     */
+    // TODO return void as it's breaking encapsulation (leaking info when exactly something deemed delivered)
+    // or demand behaves after LONG.MAX_VALUE
+    long request(long n);
+
+    /**
+     * Returns a {@linkplain Builder#subprotocols(String, String...) subprotocol}
+     * in use.
+     *
+     * @return a subprotocol, or {@code null} if there is none
+     */
+    String getSubprotocol();
+
+    /**
+     * Tells whether the {@code WebSocket} is closed.
+     *
+     * <p> A {@code WebSocket} deemed closed when either the underlying socket
+     * is closed or the closing handshake is completed.
+     *
+     * @return {@code true} if the {@code WebSocket} is closed,
+     *         {@code false} otherwise
+     */
+    boolean isClosed();
+
+    /**
+     * Closes the {@code WebSocket} abruptly.
+     *
+     * <p> This method closes the underlying TCP connection. If the {@code
+     * WebSocket} is already closed then invoking this method has no effect.
+     *
+     * @throws IOException
+     *         if an I/O error occurs
+     */
+    void abort() throws IOException;
+
+    /**
+     * A {@code WebSocket} close status code.
+     *
+     * <p> Some codes <a href="https://tools.ietf.org/html/rfc6455#section-7.4">
+     * specified</a> in the WebSocket Protocol are defined as named constants
+     * here. Others can be {@linkplain #of(int) retrieved on demand}.
+     *
+     * <p> This is a
+     * <a href="../../lang/doc-files/ValueBased.html">value-based</a> class;
+     * use of identity-sensitive operations (including reference equality
+     * ({@code ==}), identity hash code, or synchronization) on instances of
+     * {@code CloseCode} may have unpredictable results and should be avoided.
+     *
+     * @since 9
+     */
+    final class CloseCode {
+
+        /**
+         * Indicates a normal close, meaning that the purpose for which the
+         * connection was established has been fulfilled.
+         *
+         * <p> Numerical representation: {@code 1000}
+         */
+        public static final CloseCode NORMAL_CLOSURE
+                = new CloseCode(1000, "NORMAL_CLOSURE");
+
+        /**
+         * Indicates that an endpoint is "going away", such as a server going
+         * down or a browser having navigated away from a page.
+         *
+         * <p> Numerical representation: {@code 1001}
+         */
+        public static final CloseCode GOING_AWAY
+                = new CloseCode(1001, "GOING_AWAY");
+
+        /**
+         * Indicates that an endpoint is terminating the connection due to a
+         * protocol error.
+         *
+         * <p> Numerical representation: {@code 1002}
+         */
+        public static final CloseCode PROTOCOL_ERROR
+                = new CloseCode(1002, "PROTOCOL_ERROR");
+
+        /**
+         * Indicates that an endpoint is terminating the connection because it
+         * has received a type of data it cannot accept (e.g., an endpoint that
+         * understands only text data MAY send this if it receives a binary
+         * message).
+         *
+         * <p> Numerical representation: {@code 1003}
+         */
+        public static final CloseCode CANNOT_ACCEPT
+                = new CloseCode(1003, "CANNOT_ACCEPT");
+
+        /**
+         * Indicates that an endpoint is terminating the connection because it
+         * has received data within a message that was not consistent with the
+         * type of the message (e.g., non-UTF-8 [RFC3629] data within a text
+         * message).
+         *
+         * <p> Numerical representation: {@code 1007}
+         */
+        public static final CloseCode NOT_CONSISTENT
+                = new CloseCode(1007, "NOT_CONSISTENT");
+
+        /**
+         * Indicates that an endpoint is terminating the connection because it
+         * has received a message that violates its policy. This is a generic
+         * status code that can be returned when there is no other more suitable
+         * status code (e.g., {@link #CANNOT_ACCEPT} or {@link #TOO_BIG}) or if
+         * there is a need to hide specific details about the policy.
+         *
+         * <p> Numerical representation: {@code 1008}
+         */
+        public static final CloseCode VIOLATED_POLICY
+                = new CloseCode(1008, "VIOLATED_POLICY");
+
+        /**
+         * Indicates that an endpoint is terminating the connection because it
+         * has received a message that is too big for it to process.
+         *
+         * <p> Numerical representation: {@code 1009}
+         */
+        public static final CloseCode TOO_BIG
+                = new CloseCode(1009, "TOO_BIG");
+
+        /**
+         * Indicates that an endpoint is terminating the connection because it
+         * encountered an unexpected condition that prevented it from fulfilling
+         * the request.
+         *
+         * <p> Numerical representation: {@code 1011}
+         */
+        public static final CloseCode UNEXPECTED_CONDITION
+                = new CloseCode(1011, "UNEXPECTED_CONDITION");
+
+        private static final Map<Integer, CloseCode> cached = Map.ofEntries(
+                entry(NORMAL_CLOSURE),
+                entry(GOING_AWAY),
+                entry(PROTOCOL_ERROR),
+                entry(CANNOT_ACCEPT),
+                entry(NOT_CONSISTENT),
+                entry(VIOLATED_POLICY),
+                entry(TOO_BIG),
+                entry(UNEXPECTED_CONDITION)
+        );
+
+        /**
+         * Returns a {@code CloseCode} from its numerical representation.
+         *
+         * <p> The given {@code code} should be in the range {@code 1000 <= code
+         * <= 4999}, and should not be equal to any of the following codes:
+         * {@code 1004}, {@code 1005}, {@code 1006} and {@code 1015}.
+         *
+         * @param code
+         *         numerical representation
+         *
+         * @return a close code corresponding to the provided numerical value
+         *
+         * @throws IllegalArgumentException
+         *         if {@code code} violates any of the requirements above
+         */
+        public static CloseCode of(int code) {
+            if (code < 1000 || code > 4999) {
+                throw new IllegalArgumentException("Out of range: " + code);
+            }
+            if (code == 1004 || code == 1005 || code == 1006 || code == 1015) {
+                throw new IllegalArgumentException("Reserved: " + code);
+            }
+            CloseCode closeCode = cached.get(code);
+            return closeCode != null ? closeCode : new CloseCode(code, "");
+        }
+
+        private final int code;
+        private final String description;
+
+        private CloseCode(int code, String description) {
+            assert description != null;
+            this.code = code;
+            this.description = description;
+        }
+
+        /**
+         * Returns a numerical representation of this close code.
+         *
+         * @return a numerical representation
+         */
+        public int getCode() {
+            return code;
+        }
+
+        /**
+         * Compares this close code to the specified object.
+         *
+         * @param o
+         *         the object to compare this {@code CloseCode} against
+         *
+         * @return {@code true} iff the argument is a close code with the same
+         * {@linkplain #getCode() numerical representation} as this one
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof CloseCode)) {
+                return false;
+            }
+            CloseCode that = (CloseCode) o;
+            return code == that.code;
+        }
+
+        @Override
+        public int hashCode() {
+            return code;
+        }
+
+        /**
+         * Returns a human-readable representation of this close code.
+         *
+         * @apiNote The representation is not designed to be parsed; the format
+         * may change unexpectedly.
+         *
+         * @return a string representation
+         */
+        @Override
+        public String toString() {
+            return code + (description.isEmpty() ? "" : (": " + description));
+        }
+
+        private static Map.Entry<Integer, CloseCode> entry(CloseCode cc) {
+            return Map.entry(cc.getCode(), cc);
+        }
+    }
+
+    /**
+     * A character sequence that provides access to the characters UTF-8 decoded
+     * from a message in a {@code ByteBuffer}.
+     *
+     * @since 9
+     */
+    interface Text extends CharSequence {
+
+        // Methods from the CharSequence below are mentioned explicitly for the
+        // purpose of documentation, so when looking at javadoc it immediately
+        // obvious what methods Text has
+
+        @Override
+        int length();
+
+        @Override
+        char charAt(int index);
+
+        @Override
+        CharSequence subSequence(int start, int end);
+
+        /**
+         * Returns a string containing the characters in this sequence in the
+         * same order as this sequence. The length of the string will be the
+         * length of this sequence.
+         *
+         * @return a string consisting of exactly this sequence of characters
+         */
+        @Override
+        // TODO: remove the explicit javadoc above when:
+        // (JDK-8144034 has been resolved) AND (the comment is still identical
+        // to CharSequence#toString)
+        String toString();
+
+        /**
+         * Returns a read-only {@code ByteBuffer} containing the message encoded
+         * in UTF-8.
+         *
+         * @return a read-only ByteBuffer
+         */
+        ByteBuffer asByteBuffer();
+    }
+}