--- /dev/null 2016-04-22 22:42:07.067228853 +0100 +++ new/src/java.httpclient/share/classes/java/net/http/WebSocket.java 2016-04-25 23:11:10.881374094 +0100 @@ -0,0 +1,1295 @@ +/* + * 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.Future; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +/** + * A WebSocket client conforming to RFC 6455. + * + *

A {@code WebSocket} provides full-duplex communication over a TCP + * connection. + * + *

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. + * + *

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. + * + *

Messages are received only if {@linkplain #request(long) requested}. + * + *

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). + * + *

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. + * + *

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}. + * + *

All message exchange is run by the threads belonging to the {@linkplain + * HttpClient#executorService() executor service} of {@code WebSocket}'s {@link + * HttpClient}. + * + *

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}. + * + *

Equivalent to: + *

{@code
+     *     WebSocket.newBuilder(uri, HttpClient.getDefault())
+     * }
+ * + * @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}. + * + *

Providing a custom {@code client} allows for finer control over the + * opening handshake. + * + *

Example + *

{@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)...
+     * }
+ * + * @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 WebSocketBuilderImpl(uri, client, listener); + } + + /** + * A builder for creating {@code WebSocket} instances. + * + *

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}. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

If the opening handshake is not finished within the specified + * timeout then {@link #buildAsync()} completes exceptionally with a + * {@code HttpTimeoutException}. + * + *

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}. + * + *

Returns immediately with a {@code CompletableFuture} + * which completes with the {@code WebSocket} when it is connected, or + * completes exceptionally if an error occurs. + * + *

{@code CompletableFuture} may complete exceptionally with the + * following errors: + *

+ * + * @return a {@code CompletableFuture} of {@code WebSocket} + */ + CompletableFuture buildAsync(); + } + + /** + * A listener for events and messages on a {@code WebSocket}. + * + *

Each method below corresponds to a type of event. + *

+ * + *

+     *     onOpen (onText|onBinary|onPing|onPong)* (onClose|onError)?
+     * 
+ * + *

Messages received by the {@code Listener} conform to the WebSocket + * Protocol, otherwise {@code onError} with a {@link ProtocolException} is + * invoked. + * + *

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. + * + *


+     *     WHOLE|(FIRST PART* LAST)
+     * 
+ * + *

All methods are invoked in a sequential (and + * + * happens-before) 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. + * + *

When the method returns, the message is deemed received. After this + * another messages may be received. + * + *

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. + * + *

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}. + * + *

It is the responsibility of the listener to make additional + * {@linkplain WebSocket#request(long) message requests}, when ready, so + * that messages are received eventually. + * + *

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}. + * + *

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. + * + *

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. + * + *

The {@code onText} method is invoked zero or more times between + * {@code onOpen} and ({@code onClose} or {@code onError}). + * + *

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. + * + *

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. + * + *

The {@code onBinary} method is invoked zero or more times + * between {@code onOpen} and ({@code onClose} or {@code onError}). + * + *

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. + * + *

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. + * + *

The message will consist of not more than {@code 125} bytes: + * {@code message.remaining() <= 125}. + * + *

The {@code onPing} is invoked zero or more times in between + * {@code onOpen} and ({@code onClose} or {@code onError}). + * + *

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 + * + *

Replies with a Pong message if the previous Pong has been sent + * and requests one more message when this send has completed. Otherwise + * requests one more message. + * + *

The purpose of this is to not allow uncontrolled build-up of the + * outgoing queue in case where rate with which Pings are received is + * greater than rate with which Pongs are 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) { + WebSocketImpl ws = (WebSocketImpl) webSocket; + CompletionStage f = ws.sendPongIfNoOutstanding(message); + f.thenRun(() -> webSocket.request(1)); + return f; + } + + /** + * Receives a Pong message. + * + *

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. + * + *

The message will consist of not more than {@code 125} bytes: + * {@code message.remaining() <= 125}. + * + *

The {@code onPong} method is invoked zero or more times in + * between {@code onOpen} and ({@code onClose} or {@code onError}). + * + *

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. + * + *

Once a Close message is received, the server will not send any + * more messages. + * + *

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. + * + *

{@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 code, + String reason) { } + + /** + * Notifies an I/O or protocol error has occurred on the {@code + * WebSocket}. + * + *

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. + * + *

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}. + * + *

Returns immediately with a {@code CompletableFuture} which + * completes normally when the message has been sent, or completes + * exceptionally if an error occurs. + * + *

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. + * + *

The {@code ByteBuffer} should not be modified until the returned + * {@code CompletableFuture} completes (either normally or exceptionally). + * + *

The returned {@code CompletableFuture} can complete exceptionally + * with: + *

+ * + * @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 sendText(ByteBuffer message, boolean isLast); + + /** + * Sends a Text message with characters from the given {@code + * CharSequence}. + * + *

Returns immediately with a {@code CompletableFuture} which + * completes normally when the message has been sent, or completes + * exceptionally if an error occurs. + * + *

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. + * + *

The {@code CharSequence} should not be modified until the returned + * {@code CompletableFuture} completes (either normally or exceptionally). + * + *

The returned {@code CompletableFuture} can complete exceptionally + * with: + *

+ * + * @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 sendText(CharSequence message, boolean isLast); + + /** + * Sends a whole Text message with characters from the given {@code + * CharSequence}. + * + *

This is a convenience method. For the general case, use {@link + * #sendText(CharSequence, boolean)}. + * + *

Returns immediately with a {@code CompletableFuture} which + * completes normally when the message has been sent, or completes + * exceptionally if an error occurs. + * + *

The {@code CharSequence} should not be modified until the returned + * {@code CompletableFuture} completes (either normally or exceptionally). + * + *

The returned {@code CompletableFuture} can complete exceptionally + * with: + *

    + *
  • {@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 + *
+ * + * @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 sendText(CharSequence message) { + return sendText(message, true); + } + + /** + * Sends a whole Text message with characters from {@code + * CharacterSequence}s provided by the given {@code Stream}. + * + *

This is a convenience method. For the general case use {@link + * #sendText(CharSequence, boolean)}. + * + *

Returns immediately with a {@code CompletableFuture} which + * completes normally when the message has been sent, or completes + * exceptionally if an error occurs. + * + *

Streamed character sequences should not be modified until the + * returned {@code CompletableFuture} completes (either normally or + * exceptionally). + * + *

The returned {@code CompletableFuture} can complete exceptionally + * with: + *

    + *
  • {@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 + *
+ * + * @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 sendText(Stream message); + + /** + * Sends a Binary message with bytes from the given {@code ByteBuffer}. + * + *

Returns immediately with a {@code CompletableFuture} which + * completes normally when the message has been sent, or completes + * exceptionally if an error occurs. + * + *

The returned {@code CompletableFuture} can complete exceptionally + * with: + *

    + *
  • {@link IOException} + * if an I/O error occurs during this operation or the + * {@code WebSocket} closes while this operation is in progress + *
+ * + * @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 sendBinary(ByteBuffer message, boolean isLast); + + /** + * Sends a Binary message with bytes from the given {@code byte[]}. + * + *

Returns immediately with a {@code CompletableFuture} which + * completes normally when the message has been sent, or completes + * exceptionally if an error occurs. + * + *

The returned {@code CompletableFuture} can complete exceptionally + * with: + *

    + *
  • {@link IOException} + * if an I/O error occurs during this operation or the + * {@code WebSocket} closes while this operation is in progress + *
+ * + * @implSpec This is equivalent to: + *
{@code
+     *     sendBinary(ByteBuffer.wrap(message), isLast)
+     * }
+ * + * @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 sendBinary(byte[] message, boolean isLast) { + Objects.requireNonNull(message, "message"); + return sendBinary(ByteBuffer.wrap(message), isLast); + } + + /** + * Sends a Ping message. + * + *

Returns immediately with a {@code CompletableFuture} which + * completes normally when the message has been sent, or completes + * exceptionally if an error occurs. + * + *

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. + * + *

The message must consist of not more than {@code 125} bytes: {@code + * message.remaining() <= 125}. + * + *

The returned {@code CompletableFuture} can complete exceptionally + * with: + *

    + *
  • {@link IOException} + * if an I/O error occurs during this operation or the + * {@code WebSocket} closes while this operation is in progress + *
+ * + * @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 sendPing(ByteBuffer message); + + /** + * Sends a Pong message. + * + *

Returns immediately with a {@code CompletableFuture} which + * completes normally when the message has been sent, or completes + * exceptionally if an error occurs. + * + *

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. + * + *

The message must consist of not more than {@code 125} bytes: {@code + * message.remaining() <= 125}. + * + *

The returned {@code CompletableFuture} can complete exceptionally + * with: + *

    + *
  • {@link IOException} + * if an I/O error occurs during this operation or the + * {@code WebSocket} closes while this operation is in progress + *
+ * + * @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 sendPong(ByteBuffer message); + + /** + * Sends a Close message with the given close code and the reason. + * + *

Returns immediately with a {@code CompletableFuture} which + * completes normally when the message has been sent, or completes + * exceptionally if an error occurs. + * + *

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. + * + *

The returned {@code CompletableFuture} can complete exceptionally + * with: + *

    + *
  • {@link IOException} + * if an I/O error occurs during this operation or the + * {@code WebSocket} closes while this operation is in progress + *
+ * + * @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 sendClose(CloseCode code, CharSequence reason); + + /** + * Sends an empty Close message. + * + *

Returns immediately with a {@code CompletableFuture} which + * completes normally when the message has been sent, or completes + * exceptionally if an error occurs. + * + *

The returned {@code CompletableFuture} can complete exceptionally + * with: + *

    + *
  • {@link IOException} + * if an I/O error occurs during this operation or the + * {@code WebSocket} closes while this operation is in progress + *
+ * + * @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 sendClose(); + + /** + * Requests {@code n} more messages to be received by the {@link Listener + * Listener}. + * + *

The actual number might be fewer if either of the endpoints decide to + * close the connection before that or an error occurs. + * + *

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. + *

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. + * + *

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. + * + *

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. + * + *

Some codes + * specified in the WebSocket Protocol are defined as named constants + * here. Others can be {@linkplain #of(int) retrieved on demand}. + * + *

This is a + * value-based 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. + * + *

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. + * + *

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. + * + *

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). + * + *

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). + * + *

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. + * + *

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. + * + *

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. + * + *

Numerical representation: {@code 1011} + */ + public static final CloseCode UNEXPECTED_CONDITION + = new CloseCode(1011, "UNEXPECTED_CONDITION"); + + private static final Map 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. + * + *

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 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(); + } +}