OpenJDK / jdk / jdk12
changeset 38864:bf2b41533aed
8156693: Improve usability of CompletableFuture use in WebSocket API
Reviewed-by: rriggs
author | prappo |
---|---|
date | Wed, 08 Jun 2016 15:19:58 +0100 |
parents | e031aa31b25f |
children | 429c08bd6158 |
files | jdk/src/java.httpclient/share/classes/java/net/http/WS.java jdk/src/java.httpclient/share/classes/java/net/http/WSTransmitter.java jdk/src/java.httpclient/share/classes/java/net/http/WebSocket.java jdk/test/java/net/httpclient/BasicWebSocketAPITest.java |
diffstat | 4 files changed, 158 insertions(+), 171 deletions(-) [+] |
line wrap: on
line diff
--- a/jdk/src/java.httpclient/share/classes/java/net/http/WS.java Wed Jun 08 15:50:11 2016 +0200 +++ b/jdk/src/java.httpclient/share/classes/java/net/http/WS.java Wed Jun 08 15:19:58 2016 +0100 @@ -86,7 +86,7 @@ } } }; - transmitter = new WSTransmitter(executor, channel, errorHandler); + transmitter = new WSTransmitter(this, executor, channel, errorHandler); receiver = new WSReceiver(this.listener, this, executor, channel); } @@ -95,7 +95,7 @@ } @Override - public CompletableFuture<Void> sendText(CharSequence message, boolean isLast) { + public CompletableFuture<WebSocket> sendText(CharSequence message, boolean isLast) { requireNonNull(message, "message"); synchronized (stateLock) { checkState(); @@ -104,7 +104,7 @@ } @Override - public CompletableFuture<Void> sendText(Stream<? extends CharSequence> message) { + public CompletableFuture<WebSocket> sendText(Stream<? extends CharSequence> message) { requireNonNull(message, "message"); synchronized (stateLock) { checkState(); @@ -113,7 +113,7 @@ } @Override - public CompletableFuture<Void> sendBinary(ByteBuffer message, boolean isLast) { + public CompletableFuture<WebSocket> sendBinary(ByteBuffer message, boolean isLast) { requireNonNull(message, "message"); synchronized (stateLock) { checkState(); @@ -122,7 +122,7 @@ } @Override - public CompletableFuture<Void> sendPing(ByteBuffer message) { + public CompletableFuture<WebSocket> sendPing(ByteBuffer message) { requireNonNull(message, "message"); synchronized (stateLock) { checkState(); @@ -131,7 +131,7 @@ } @Override - public CompletableFuture<Void> sendPong(ByteBuffer message) { + public CompletableFuture<WebSocket> sendPong(ByteBuffer message) { requireNonNull(message, "message"); synchronized (stateLock) { checkState(); @@ -140,7 +140,7 @@ } @Override - public CompletableFuture<Void> sendClose(CloseCode code, CharSequence reason) { + public CompletableFuture<WebSocket> sendClose(CloseCode code, CharSequence reason) { requireNonNull(code, "code"); requireNonNull(reason, "reason"); synchronized (stateLock) { @@ -149,13 +149,13 @@ } @Override - public CompletableFuture<Void> sendClose() { + public CompletableFuture<WebSocket> sendClose() { synchronized (stateLock) { return doSendClose(() -> transmitter.sendClose()); } } - private CompletableFuture<Void> doSendClose(Supplier<CompletableFuture<Void>> s) { + private CompletableFuture<WebSocket> doSendClose(Supplier<CompletableFuture<WebSocket>> s) { checkState(); boolean closeChannel = false; synchronized (stateLock) { @@ -165,7 +165,7 @@ tryChangeState(State.CLOSED_LOCALLY); } } - CompletableFuture<Void> sent = s.get(); + CompletableFuture<WebSocket> sent = s.get(); if (closeChannel) { sent.whenComplete((v, t) -> { try {
--- a/jdk/src/java.httpclient/share/classes/java/net/http/WSTransmitter.java Wed Jun 08 15:50:11 2016 +0200 +++ b/jdk/src/java.httpclient/share/classes/java/net/http/WSTransmitter.java Wed Jun 08 15:19:58 2016 +0100 @@ -51,15 +51,17 @@ */ final class WSTransmitter { - private final BlockingQueue<Pair<WSOutgoingMessage, CompletableFuture<Void>>> + private final BlockingQueue<Pair<WSOutgoingMessage, CompletableFuture<WebSocket>>> backlog = new LinkedBlockingQueue<>(); private final WSMessageSender sender; private final WSSignalHandler handler; + private final WebSocket webSocket; private boolean previousMessageSent = true; private boolean canSendBinary = true; private boolean canSendText = true; - WSTransmitter(Executor executor, RawChannel channel, Consumer<Throwable> errorHandler) { + WSTransmitter(WebSocket ws, Executor executor, RawChannel channel, Consumer<Throwable> errorHandler) { + this.webSocket = ws; this.handler = new WSSignalHandler(executor, this::handleSignal); Consumer<Throwable> sendCompletion = (error) -> { synchronized (this) { @@ -76,41 +78,41 @@ this.sender = new WSMessageSender(channel, sendCompletion); } - CompletableFuture<Void> sendText(CharSequence message, boolean isLast) { + CompletableFuture<WebSocket> sendText(CharSequence message, boolean isLast) { checkAndUpdateText(isLast); return acceptMessage(new Text(isLast, message)); } - CompletableFuture<Void> sendText(Stream<? extends CharSequence> message) { + CompletableFuture<WebSocket> sendText(Stream<? extends CharSequence> message) { checkAndUpdateText(true); return acceptMessage(new StreamedText(message)); } - CompletableFuture<Void> sendBinary(ByteBuffer message, boolean isLast) { + CompletableFuture<WebSocket> sendBinary(ByteBuffer message, boolean isLast) { checkAndUpdateBinary(isLast); return acceptMessage(new Binary(isLast, message)); } - CompletableFuture<Void> sendPing(ByteBuffer message) { + CompletableFuture<WebSocket> sendPing(ByteBuffer message) { checkSize(message.remaining(), 125); return acceptMessage(new Ping(message)); } - CompletableFuture<Void> sendPong(ByteBuffer message) { + CompletableFuture<WebSocket> sendPong(ByteBuffer message) { checkSize(message.remaining(), 125); return acceptMessage(new Pong(message)); } - CompletableFuture<Void> sendClose(WebSocket.CloseCode code, CharSequence reason) { + CompletableFuture<WebSocket> sendClose(WebSocket.CloseCode code, CharSequence reason) { return acceptMessage(createCloseMessage(code, reason)); } - CompletableFuture<Void> sendClose() { + CompletableFuture<WebSocket> sendClose() { return acceptMessage(new Close(ByteBuffer.allocate(0))); } - private CompletableFuture<Void> acceptMessage(WSOutgoingMessage m) { - CompletableFuture<Void> cf = new CompletableFuture<>(); + private CompletableFuture<WebSocket> acceptMessage(WSOutgoingMessage m) { + CompletableFuture<WebSocket> cf = new CompletableFuture<>(); synchronized (this) { backlog.offer(pair(m, cf)); } @@ -123,11 +125,11 @@ synchronized (this) { while (!backlog.isEmpty() && previousMessageSent) { previousMessageSent = false; - Pair<WSOutgoingMessage, CompletableFuture<Void>> p = backlog.peek(); + Pair<WSOutgoingMessage, CompletableFuture<WebSocket>> p = backlog.peek(); boolean sent = sender.trySendFully(p.first); if (sent) { backlog.remove(); - p.second.complete(null); + p.second.complete(webSocket); previousMessageSent = true; } }
--- a/jdk/src/java.httpclient/share/classes/java/net/http/WebSocket.java Wed Jun 08 15:50:11 2016 +0200 +++ b/jdk/src/java.httpclient/share/classes/java/net/http/WebSocket.java Wed Jun 08 15:19:58 2016 +0100 @@ -52,8 +52,8 @@ * * <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. + * asynchronously. Each of the methods returns a {@link CompletionStage} which + * completes when the operation has completed. * * <p> Messages are received only if {@linkplain #request(long) requested}. * @@ -79,6 +79,9 @@ * or method of this type will cause a {@link NullPointerException * NullPointerException} to be thrown. * + * @implNote The default implementation's methods do not block before returning + * a {@code CompletableFuture}. + * * @since 9 */ public interface WebSocket { @@ -234,9 +237,9 @@ /** * 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> Returns a {@code CompletableFuture<WebSocket>} which completes + * normally 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: @@ -252,7 +255,7 @@ * if the opening handshake fails * </ul> * - * @return a {@code CompletableFuture} of {@code WebSocket} + * @return a {@code CompletableFuture} with the {@code WebSocket} */ CompletableFuture<WebSocket> buildAsync(); } @@ -601,9 +604,9 @@ /** * 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> Returns a {@code CompletableFuture<WebSocket>} 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). @@ -612,9 +615,12 @@ * 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 + * if an I/O error occurs during this operation + * <li> {@link IllegalStateException} + * if the {@code WebSocket} closes while this operation is in progress; + * or if a Close message has been sent already; + * or if there is an outstanding send operation; + * or if a previous Binary message was not sent with {@code isLast == true} * </ul> * * @implNote This implementation does not accept partial UTF-16 @@ -624,22 +630,15 @@ * @param message * the message * @param isLast - * {@code true} if this is the final part of the message + * {@code true} if this is the final part of the message, * {@code false} otherwise * - * @return a CompletableFuture of Void + * @return a CompletableFuture with this WebSocket * - * @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} + * @throws IllegalArgumentException + * if {@code message} is a malformed (or an incomplete) UTF-16 sequence */ - CompletableFuture<Void> sendText(CharSequence message, boolean isLast); + CompletableFuture<WebSocket> sendText(CharSequence message, boolean isLast); /** * Sends a whole Text message with characters from the given {@code @@ -648,9 +647,9 @@ * <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> Returns a {@code CompletableFuture<WebSocket>} 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). @@ -659,27 +658,23 @@ * 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 (or an incomplete) UTF-16 sequence + * if an I/O error occurs during this operation + * <li> {@link IllegalStateException} + * if the {@code WebSocket} closes while this operation is in progress; + * or if a Close message has been sent already; + * or if there is an outstanding send operation; + * or if a previous Binary message was not sent with {@code isLast == true} * </ul> * * @param message * the message * - * @return a CompletableFuture of Void + * @return a CompletableFuture with this WebSocket * - * @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} + * @throws IllegalArgumentException + * if {@code message} is a malformed (or an incomplete) UTF-16 sequence */ - default CompletableFuture<Void> sendText(CharSequence message) { + default CompletableFuture<WebSocket> sendText(CharSequence message) { return sendText(message, true); } @@ -690,9 +685,9 @@ * <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> Returns a {@code CompletableFuture<WebSocket>} 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 @@ -702,41 +697,41 @@ * 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 (or an incomplete) UTF-16 sequence + * if an I/O error occurs during this operation + * <li> {@link IllegalStateException} + * if the {@code WebSocket} closes while this operation is in progress; + * or if a Close message has been sent already; + * or if there is an outstanding send operation; + * or if a previous Binary message was not sent with {@code isLast == true} * </ul> * * @param message * the message * - * @return a CompletableFuture of Void + * @return a CompletableFuture with this WebSocket * - * @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} + * @throws IllegalArgumentException + * if {@code message} is a malformed (or an incomplete) UTF-16 sequence */ - CompletableFuture<Void> sendText(Stream<? extends CharSequence> message); + CompletableFuture<WebSocket> 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> Returns a {@code CompletableFuture<WebSocket>} 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 + * if an I/O error occurs during this operation + * <li> {@link IllegalStateException} + * if the {@code WebSocket} closes while this operation is in progress; + * or if a Close message has been sent already; + * or if there is an outstanding send operation; + * or if a previous Text message was not sent with {@code isLast == true} * </ul> * * @param message @@ -745,33 +740,27 @@ * {@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} + * @return a CompletableFuture with this WebSocket */ - CompletableFuture<Void> sendBinary(ByteBuffer message, boolean isLast); + CompletableFuture<WebSocket> 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> Returns a {@code CompletableFuture<WebSocket>} 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 + * if an I/O error occurs during this operation + * <li> {@link IllegalStateException} + * if the {@code WebSocket} closes while this operation is in progress; + * or if a Close message has been sent already; + * or if there is an outstanding send operation; + * or if a previous Text message was not sent with {@code isLast == true} * </ul> * * @implSpec This is equivalent to: @@ -785,19 +774,9 @@ * {@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} + * @return a CompletableFuture with this WebSocket */ - default CompletableFuture<Void> sendBinary(byte[] message, boolean isLast) { + default CompletableFuture<WebSocket> sendBinary(byte[] message, boolean isLast) { Objects.requireNonNull(message, "message"); return sendBinary(ByteBuffer.wrap(message), isLast); } @@ -805,9 +784,9 @@ /** * 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> Returns a {@code CompletableFuture<WebSocket>} 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 @@ -820,32 +799,29 @@ * 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 + * if an I/O error occurs during this operation + * <li> {@link IllegalStateException} + * if the {@code WebSocket} closes while this operation is in progress; + * or if a Close message has been sent already; + * or if there is an outstanding send operation * </ul> * * @param message * the message * - * @return a CompletableFuture of Void + * @return a CompletableFuture with this WebSocket * - * @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); + CompletableFuture<WebSocket> 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> Returns a {@code CompletableFuture<WebSocket>} 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 @@ -858,32 +834,29 @@ * 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 + * if an I/O error occurs during this operation + * <li> {@link IllegalStateException} + * if the {@code WebSocket} closes while this operation is in progress; + * or if a Close message has been sent already; + * or if there is an outstanding send operation * </ul> * * @param message * the message * - * @return a CompletableFuture of Void + * @return a CompletableFuture with this WebSocket * - * @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); + CompletableFuture<WebSocket> 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> Returns a {@code CompletableFuture<WebSocket>} 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 @@ -894,8 +867,11 @@ * 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 + * if an I/O error occurs during this operation + * <li> {@link IllegalStateException} + * if the {@code WebSocket} closes while this operation is in progress; + * or if a Close message has been sent already; + * or if there is an outstanding send operation * </ul> * * @param code @@ -903,45 +879,35 @@ * @param reason * the reason; can be empty * - * @return a CompletableFuture of Void + * @return a CompletableFuture with this WebSocket * - * @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 + * if {@code reason} doesn't have an UTF-8 representation not longer + * than {@code 123} bytes */ - CompletableFuture<Void> sendClose(CloseCode code, CharSequence reason); + CompletableFuture<WebSocket> 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> Returns a {@code CompletableFuture<WebSocket>} 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 + * if an I/O error occurs during this operation + * <li> {@link IllegalStateException} + * if the {@code WebSocket} closes while this operation is in progress; + * or if a Close message has been sent already; + * or if there is an outstanding send operation * </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 + * @return a CompletableFuture with this WebSocket */ - CompletableFuture<Void> sendClose(); + CompletableFuture<WebSocket> sendClose(); /** * Requests {@code n} more messages to be received by the {@link Listener
--- a/jdk/test/java/net/httpclient/BasicWebSocketAPITest.java Wed Jun 08 15:50:11 2016 +0200 +++ b/jdk/test/java/net/httpclient/BasicWebSocketAPITest.java Wed Jun 08 15:19:58 2016 +0100 @@ -30,6 +30,7 @@ import java.net.http.WebSocket; import java.net.http.WebSocket.CloseCode; import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.channels.SocketChannel; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -92,12 +93,24 @@ ); checkAndClose( (ws) -> + TestKit.assertThrows(IllegalArgumentException.class, + ".*message.*", + () -> ws.sendPing(ByteBuffer.allocate(126))) + ); + checkAndClose( + (ws) -> TestKit.assertThrows(NullPointerException.class, "message", () -> ws.sendPing(null)) ); checkAndClose( (ws) -> + TestKit.assertThrows(IllegalArgumentException.class, + ".*message.*", + () -> ws.sendPong(ByteBuffer.allocate(126))) + ); + checkAndClose( + (ws) -> TestKit.assertThrows(NullPointerException.class, "message", () -> ws.sendPong(null)) @@ -106,7 +119,7 @@ (ws) -> TestKit.assertThrows(NullPointerException.class, "message", - () -> ws.sendText((CharSequence) null, true)) + () -> ws.sendText(null, true)) ); checkAndClose( (ws) -> @@ -122,6 +135,12 @@ ); checkAndClose( (ws) -> + TestKit.assertThrows(IllegalArgumentException.class, + "(?i).*reason.*", + () -> ws.sendClose(CloseCode.NORMAL_CLOSURE, CharBuffer.allocate(124))) + ); + checkAndClose( + (ws) -> TestKit.assertThrows(NullPointerException.class, "code", () -> ws.sendClose(null, ""))