http-client-branch: (WebSocket) builder change http-client-branch
authorprappo
Sat, 18 Nov 2017 20:13:09 +0300
branchhttp-client-branch
changeset 55838 12a64276cc96
parent 55837 0262f29eef0d
child 55839 d4142ab48c8a
http-client-branch: (WebSocket) builder change
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientFacade.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocket.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/BuilderImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketImpl.java
test/jdk/java/net/httpclient/examples/WebSocketExample.java
test/jdk/java/net/httpclient/websocket/ConnectionHandover.java
test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/BuildingWebSocketTest.java
test/jdk/java/net/httpclient/websocket/security/WSURLPermissionTest.java
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java	Fri Nov 17 18:38:05 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java	Sat Nov 18 20:13:09 2017 +0300
@@ -480,15 +480,13 @@
     sendAsync(HttpRequest req, HttpResponse.MultiSubscriber<U, T> multiSubscriber);
 
     /**
-     * Creates a builder of {@link WebSocket} instances connected to the given
-     * URI and receiving events and messages with the given {@code Listener}.
+     * Creates a new {@code WebSocket} builder (optional operation).
      *
      * <p> <b>Example</b>
      * <pre>{@code
      *     HttpClient client = HttpClient.newHttpClient();
-     *     WebSocket.Builder builder = client.newWebSocketBuilder(
-     *             URI.create("ws://websocket.example.com"),
-     *             listener);
+     *     CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+     *             .buildAsync(URI.create("ws://websocket.example.com"), listener);
      * }</pre>
      *
      * <p> Finer control over the WebSocket Opening Handshake can be achieved
@@ -500,29 +498,41 @@
      *     HttpClient client = HttpClient.newBuilder()
      *             .proxy(ProxySelector.of(addr))
      *             .build();
-     *     WebSocket.Builder builder = client.newWebSocketBuilder(
-     *             URI.create("ws://websocket.example.com"),
-     *             listener);
+     *     CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+     *             .buildAsync(URI.create("ws://websocket.example.com"), listener);
      * }</pre>
      *
-     * @implSpec The default implementation of this method throws {@code
-     * UnsupportedOperationException}. However, clients obtained through
+     * <p> A {@code WebSocket.Builder} returned from this method is not safe for
+     * use by multiple threads without external synchronization.
+     *
+     * @implSpec The default implementation of this method throws
+     * {@code UnsupportedOperationException}. Clients obtained through
      * {@link HttpClient#newHttpClient()} or {@link HttpClient#newBuilder()}
-     * provide WebSocket capability.
+     * return a {@code WebSocket} builder.
+     *
+     * @implNote Both builder and {@code WebSocket}s created with it operate in
+     * a non-blocking fashion. That is, their methods do not block before
+     * returning a {@code CompletableFuture}. Asynchronous tasks executed in
+     * this {@code HttpClient}'s executor.
      *
-     * @param uri
-     *         the WebSocket URI
-     * @param listener
-     *         the listener
+     * <p> {@code WebSocket} does not allow to send Text messages that are
+     * partial UTF-16 sequences. If such a sequence is passed, a
+     * {@code CompletableFuture} returned from {@link WebSocket#sendText} will
+     * complete exceptionally with {@code IOException}.
+     * Similarly, {@code WebSocket} invokes
+     * {@link WebSocket.Listener#onText Listener.onText} with messages which
+     * are complete UTF-16 sequences.
      *
-     * @return a builder of {@code WebSocket} instances
+     * <p> When a {@code CompletionStage} returned from
+     * {@link WebSocket.Listener#onClose Listener.onClose} completes,
+     * the {@code WebSocket} will send a Close message that has the same code
+     * the received message has and an empty reason.
+     *
+     * @return a {@code WebSocket.Builder}
      * @throws UnsupportedOperationException
      *         if this {@code HttpClient} does not provide WebSocket support
-     * @throws IllegalArgumentException if the given URI is not a valid WebSocket URI
      */
-    public WebSocket.Builder newWebSocketBuilder(URI uri,
-                                                 WebSocket.Listener listener)
-    {
+    public WebSocket.Builder newWebSocketBuilder() {
         throw new UnsupportedOperationException();
     }
 }
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientFacade.java	Fri Nov 17 18:38:05 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientFacade.java	Sat Nov 18 20:13:09 2017 +0300
@@ -125,11 +125,9 @@
     }
 
     @Override
-    public WebSocket.Builder newWebSocketBuilder(URI uri,
-                                                 WebSocket.Listener listener)
-    {
+    public WebSocket.Builder newWebSocketBuilder() {
         try {
-            return impl.newWebSocketBuilder(uri, listener);
+            return impl.newWebSocketBuilder();
         } finally {
             Reference.reachabilityFence(this);
         }
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientImpl.java	Fri Nov 17 18:38:05 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientImpl.java	Sat Nov 18 20:13:09 2017 +0300
@@ -925,14 +925,13 @@
     }
 
     @Override
-    public WebSocket.Builder newWebSocketBuilder(URI uri,
-                                                 WebSocket.Listener listener) {
-        // Make sure to pass the HttpClientFacade to the web socket builder.
+    public WebSocket.Builder newWebSocketBuilder() {
+        // Make sure to pass the HttpClientFacade to the WebSocket builder.
         // This will ensure that the facade is not released before the
         // WebSocket has been created, at which point the pendingOperationCount
         // will have been incremented by the DetachedConnectionChannel
         // (see PlainHttpConnection.detachChannel())
-        return new BuilderImpl(this.facade(), uri, listener, proxySelector);
+        return new BuilderImpl(this.facade(), proxySelector);
     }
 
     @Override
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocket.java	Fri Nov 17 18:38:05 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocket.java	Sat Nov 18 20:13:09 2017 +0300
@@ -97,29 +97,24 @@
      * A builder for creating {@code WebSocket} instances.
      * {@Incubating}
      *
-     * <p> To build a {@code WebSocket}, {@linkplain HttpClient#newWebSocketBuilder(
-     * URI, Listener) create} a builder, configure it as required by
+     * <p> To obtain a {@code WebSocket} configure a builder 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}.
+     * then call {@code buildAsync()}. If an intermediate method is not called,
+     * an appropriate default value (or behavior) will be assumed.
      *
-     * <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).
-     *
-     * <p> Instances of {@code Builder} are not safe for use by multiple threads
-     * without external synchronization.
+     * <p> Unless otherwise stated, {@code null} arguments will cause methods of
+     * {@code Builder} to throw {@code NullPointerException}.
      *
      * @since 9
      */
     interface Builder {
 
         /**
-         * Adds the given name-value pair to the list of additional headers for
-         * the opening handshake.
+         * Adds the given name-value pair to the list of additional HTTP headers
+         * sent during the opening handshake.
          *
-         * <p> Headers defined in WebSocket Protocol are not allowed to be added.
+         * <p> Headers defined in WebSocket Protocol are illegal. If this method
+         * is not invoked, no additional HTTP headers will be sent.
          *
          * @param name
          *         the header name
@@ -131,38 +126,12 @@
         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. This subprotocol will be available from {@link
-         * WebSocket#getSubprotocol}. Subprotocols are specified in the order of
-         * preference.
-         *
-         * <p> Each of the given subprotocols must conform to the relevant
-         * rules defined in the WebSocket Protocol.
-         *
-         * <p> If this method is not invoked then no subprotocols are requested.
+         * Sets a timeout for establishing a WebSocket connection.
          *
-         * @param mostPreferred
-         *         the most preferred subprotocol
-         * @param lesserPreferred
-         *         the lesser preferred subprotocols, with the least preferred
-         *         at the end
-         *
-         * @return this builder
-         */
-        Builder subprotocols(String mostPreferred, String... lesserPreferred);
-
-        /**
-         * Sets a timeout for the opening handshake.
-         *
-         * <p> If the opening handshake does not complete within the specified
-         * duration then the {@code CompletableFuture} returned from {@link
-         * #buildAsync()} completes exceptionally with a {@link
-         * HttpTimeoutException}.
-         *
-         * <p> If this method is not invoked then the timeout is deemed infinite.
+         * <p> If the connection is not established within the specified
+         * duration then building of the {@code WebSocket} will fail with
+         * {@link HttpTimeoutException}. If this method is not invoked then the
+         * infinite timeout is assumed.
          *
          * @param timeout
          *         the timeout, non-{@linkplain Duration#isNegative() negative},
@@ -173,14 +142,37 @@
         Builder connectTimeout(Duration timeout);
 
         /**
-         * Builds a {@code WebSocket}.
+         * Sets a request for the given subprotocols.
+         *
+         * <p> After the {@code WebSocket} has been built, the actual
+         * subprotocol can be queried via
+         * {@link WebSocket#getSubprotocol WebSocket.getSubprotocol()}.
+         *
+         * <p> Subprotocols are specified in the order of preference. The most
+         * preferred subprotocol is specified first. If there are any additional
+         * subprotocols they are enumerated from the most preferred to the least
+         * preferred.
+         *
+         * <p> Subprotocols not conforming to the syntax of subprotocol
+         * identifiers are illegal. If this method is not invoked then no
+         * subprotocols will be requested.
          *
-         * <p> Returns a {@code CompletableFuture<WebSocket>} which completes
-         * normally with the {@code WebSocket} when it is connected or completes
-         * exceptionally if an error occurs.
+         * @param mostPreferred
+         *         the most preferred subprotocol
+         * @param lesserPreferred
+         *         the lesser preferred subprotocols
          *
-         * <p> {@code CompletableFuture} may complete exceptionally with the
-         * following errors:
+         * @return this builder
+         */
+        Builder subprotocols(String mostPreferred, String... lesserPreferred);
+
+        /**
+         * Builds a {@link WebSocket} connected to the given {@code URI} and
+         * associated with the given {@code Listener}.
+         *
+         * <p> Returns a {@code CompletableFuture} which will either complete
+         * normally with the resulting {@code WebSocket} or complete
+         * exceptionally with one of the following errors:
          * <ul>
          * <li> {@link IOException} -
          *          if an I/O error occurs
@@ -188,28 +180,28 @@
          *          if the opening handshake fails
          * <li> {@link HttpTimeoutException} -
          *          if the opening handshake does not complete within
-         *          the specified {@linkplain #connectTimeout(Duration) duration}
+         *          the timeout
          * <li> {@link InterruptedException} -
-         *          if the operation was interrupted
+         *          if the operation is interrupted
          * <li> {@link SecurityException} -
-         *          If a security manager has been installed and it denies
-         *          {@link java.net.URLPermission access} to the WebSocket URI,
-         *          that was used to create this builder.
+         *          if a security manager has been installed and it denies
+         *          {@link java.net.URLPermission access} to {@code uri}.
          *          <a href="HttpRequest.html#securitychecks">Security checks</a>
          *          contains more information relating to the security context
          *          in which the the listener is invoked.
          * <li> {@link IllegalArgumentException} -
-         *          if any of the additional {@link #header(String, String)
-         *          headers} are illegal;
-         *          or if any of the WebSocket Protocol rules relevant to {@link
-         *          #subprotocols(String, String...) subprotocols} are violated;
-         *          or if the {@link #connectTimeout(Duration) connect timeout}
-         *          is invalid;
+         *          if any of the arguments of this builder's methods are
+         *          illegal
          * </ul>
          *
+         * @param uri
+         *         the WebSocket URI
+         * @param listener
+         *         the listener
+         *
          * @return a {@code CompletableFuture} with the {@code WebSocket}
          */
-        CompletableFuture<WebSocket> buildAsync();
+        CompletableFuture<WebSocket> buildAsync(URI uri, Listener listener);
     }
 
     /**
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/BuilderImpl.java	Fri Nov 17 18:38:05 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/BuilderImpl.java	Sat Nov 18 20:13:09 2017 +0300
@@ -40,26 +40,22 @@
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 
-import static java.lang.String.format;
 import static java.util.Objects.requireNonNull;
 import static jdk.incubator.http.internal.common.Pair.pair;
 
 public final class BuilderImpl implements Builder {
 
     private final HttpClient client;
-    private final URI uri;
-    private final Listener listener;
+    private URI uri;
+    private Listener listener;
     private final Optional<ProxySelector> proxySelector;
     private final Collection<Pair<String, String>> headers;
     private final Collection<String> subprotocols;
     private Duration timeout;
 
-    public BuilderImpl(HttpClient client,
-                       URI uri,
-                       Listener listener,
-                       ProxySelector proxySelector)
+    public BuilderImpl(HttpClient client, ProxySelector proxySelector)
     {
-        this(client, uri, listener, Optional.ofNullable(proxySelector),
+        this(client, null, null, Optional.ofNullable(proxySelector),
              new LinkedList<>(), new LinkedList<>(), null);
     }
 
@@ -70,37 +66,19 @@
                         Collection<Pair<String, String>> headers,
                         Collection<String> subprotocols,
                         Duration timeout) {
-        this.client = requireNonNull(client, "client");
-        this.uri = checkURI(requireNonNull(uri, "uri"));
-        this.listener = requireNonNull(listener, "listener");
+        this.client = client;
+        this.uri = uri;
+        this.listener = listener;
         this.proxySelector = proxySelector;
         // If a proxy selector was supplied by the user, it should be present
         // on the client and should be the same that what we got as an argument
         assert !client.proxy().isPresent()
                 || client.proxy().equals(proxySelector);
-        this.headers = requireNonNull(headers);
-        this.subprotocols = requireNonNull(subprotocols);
+        this.headers = headers;
+        this.subprotocols = subprotocols;
         this.timeout = timeout;
     }
 
-    private static IllegalArgumentException newIAE(String message, Object... args) {
-        return new IllegalArgumentException(format(message, args));
-    }
-
-    private static URI checkURI(URI uri) {
-        String scheme = uri.getScheme();
-        if (scheme == null)
-            throw newIAE("URI with undefined scheme");
-        scheme = scheme.toLowerCase();
-        if (!(scheme.equals("ws") || scheme.equals("wss")))
-            throw newIAE("invalid URI scheme %s", scheme);
-        if (uri.getHost() == null)
-            throw newIAE("URI must contain a host: %s", uri);
-        if (uri.getFragment() != null)
-            throw newIAE("URI must not contain a fragment: %s", uri);
-        return uri;
-    }
-
     @Override
     public Builder header(String name, String value) {
         requireNonNull(name, "name");
@@ -110,8 +88,7 @@
     }
 
     @Override
-    public Builder subprotocols(String mostPreferred,
-                                String... lesserPreferred)
+    public Builder subprotocols(String mostPreferred, String... lesserPreferred)
     {
         requireNonNull(mostPreferred, "mostPreferred");
         requireNonNull(lesserPreferred, "lesserPreferred");
@@ -134,7 +111,11 @@
     }
 
     @Override
-    public CompletableFuture<WebSocket> buildAsync() {
+    public CompletableFuture<WebSocket> buildAsync(URI uri, Listener listener) {
+        this.uri = requireNonNull(uri, "uri");
+        this.listener = requireNonNull(listener, "listener");
+        // A snapshot of builder inaccessible for further modification
+        // from the outside
         BuilderImpl copy = immutableCopy();
         return WebSocketImpl.newInstanceAsync(copy);
     }
@@ -153,7 +134,7 @@
 
     Optional<ProxySelector> getProxySelector() { return proxySelector; }
 
-    BuilderImpl immutableCopy() {
+    private BuilderImpl immutableCopy() {
         @SuppressWarnings({"unchecked", "rawtypes"})
         BuilderImpl copy = new BuilderImpl(
                 client,
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketImpl.java	Fri Nov 17 18:38:05 2017 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketImpl.java	Sat Nov 18 20:13:09 2017 +0300
@@ -59,6 +59,7 @@
 import jdk.incubator.http.internal.websocket.OutgoingMessage.Pong;
 import jdk.incubator.http.internal.websocket.OutgoingMessage.Text;
 
+import static java.lang.String.format;
 import static java.util.Objects.requireNonNull;
 import static java.util.concurrent.CompletableFuture.failedFuture;
 import static java.util.stream.Collectors.joining;
@@ -159,7 +160,30 @@
         }
     }
 
+    private static IllegalArgumentException newIAE(String message, Object... args) {
+        return new IllegalArgumentException(format(message, args));
+    }
+
+    private static URI checkURI(URI uri) {
+        String scheme = uri.getScheme();
+        if (scheme == null)
+            throw newIAE("URI with undefined scheme");
+        scheme = scheme.toLowerCase();
+        if (!(scheme.equals("ws") || scheme.equals("wss")))
+            throw newIAE("invalid URI scheme %s", scheme);
+        if (uri.getHost() == null)
+            throw newIAE("URI must contain a host: %s", uri);
+        if (uri.getFragment() != null)
+            throw newIAE("URI must not contain a fragment: %s", uri);
+        return uri;
+    }
+
     static CompletableFuture<WebSocket> newInstanceAsync(BuilderImpl b) {
+        try {
+            checkURI(b.getUri());
+        } catch (IllegalArgumentException e) {
+            return failedFuture(e);
+        }
         Proxy proxy = proxyFor(b.getProxySelector(), b.getUri());
         try {
             checkPermissions(b, proxy);
--- a/test/jdk/java/net/httpclient/examples/WebSocketExample.java	Fri Nov 17 18:38:05 2017 +0000
+++ b/test/jdk/java/net/httpclient/examples/WebSocketExample.java	Sat Nov 18 20:13:09 2017 +0300
@@ -24,6 +24,8 @@
 import java.net.InetSocketAddress;
 import java.net.ProxySelector;
 import java.net.URI;
+import java.util.concurrent.CompletableFuture;
+
 import jdk.incubator.http.HttpClient;
 import jdk.incubator.http.WebSocket;
 
@@ -42,9 +44,8 @@
 
     public void newBuilderExample0() {
         HttpClient client = HttpClient.newHttpClient();
-        WebSocket.Builder builder = client.newWebSocketBuilder(
-                URI.create("ws://websocket.example.com"),
-                listener);
+        CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+                .buildAsync(URI.create("ws://websocket.example.com"), listener);
     }
 
     public void newBuilderExample1() {
@@ -52,8 +53,7 @@
         HttpClient client = HttpClient.newBuilder()
                 .proxy(ProxySelector.of(addr))
                 .build();
-        WebSocket.Builder builder = client.newWebSocketBuilder(
-                URI.create("ws://websocket.example.com"),
-                listener);
+        CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+                .buildAsync(URI.create("ws://websocket.example.com"), listener);
     }
 }
--- a/test/jdk/java/net/httpclient/websocket/ConnectionHandover.java	Fri Nov 17 18:38:05 2017 +0000
+++ b/test/jdk/java/net/httpclient/websocket/ConnectionHandover.java	Sat Nov 18 20:13:09 2017 +0300
@@ -48,14 +48,16 @@
             server.open();
             URI uri = server.getURI();
             WebSocket.Builder webSocketBuilder =
-                    HttpClient.newHttpClient().newWebSocketBuilder(uri, new WebSocket.Listener() { });
+                    HttpClient.newHttpClient().newWebSocketBuilder();
 
-            WebSocket ws1 = webSocketBuilder.buildAsync().join();
+            WebSocket ws1 = webSocketBuilder
+                    .buildAsync(uri, new WebSocket.Listener() { }).join();
             try {
                 ws1.abort();
             } catch (IOException ignored) { }
 
-            WebSocket ws2 = webSocketBuilder.buildAsync().join(); // Exception here if the connection was pooled
+            WebSocket ws2 = webSocketBuilder
+                    .buildAsync(uri, new WebSocket.Listener() { }).join(); // Exception here if the connection was pooled
             try {
                 ws2.abort();
             } catch (IOException ignored) { }
--- a/test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/BuildingWebSocketTest.java	Fri Nov 17 18:38:05 2017 +0000
+++ b/test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/BuildingWebSocketTest.java	Sat Nov 18 20:13:09 2017 +0300
@@ -34,6 +34,7 @@
 import java.util.concurrent.CompletionStage;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static jdk.incubator.http.internal.websocket.TestSupport.assertCompletesExceptionally;
 import static jdk.incubator.http.internal.websocket.TestSupport.assertThrows;
@@ -45,71 +46,77 @@
  */
 public class BuildingWebSocketTest {
 
+    private final static URI VALID_URI = URI.create("ws://websocket.example.com");
+
     @Test
-    public void nulls() {
+    public void nullArguments() {
         HttpClient c = HttpClient.newHttpClient();
-        URI uri = URI.create("ws://websocket.example.com");
 
         assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(null, listener()));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(uri, null));
+                     () -> c.newWebSocketBuilder()
+                            .buildAsync(null, listener()));
         assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(null, null));
+                     () -> c.newWebSocketBuilder()
+                            .buildAsync(VALID_URI, null));
         assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(uri, listener())
+                     () -> c.newWebSocketBuilder()
+                            .buildAsync(null, null));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
                             .header(null, "value"));
         assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(uri, listener())
+                     () -> c.newWebSocketBuilder()
                             .header("name", null));
         assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(uri, listener())
+                     () -> c.newWebSocketBuilder()
                             .header(null, null));
         assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(uri, listener())
+                     () -> c.newWebSocketBuilder()
                             .subprotocols(null));
         assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(uri, listener())
-                            .subprotocols(null, "sub1"));
+                     () -> c.newWebSocketBuilder()
+                            .subprotocols(null, "sub2.example.com"));
         assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(uri, listener())
+                     () -> c.newWebSocketBuilder()
                             .subprotocols("sub1.example.com", (String) null));
         assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(uri, listener())
+                     () -> c.newWebSocketBuilder()
                             .subprotocols("sub1.example.com", (String[]) null));
         assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(uri, listener())
-                            .subprotocols("sub1.example.com",
-                                          "sub2.example.com",
-                                          null));
+                     () -> c.newWebSocketBuilder()
+                            .subprotocols("sub1.example.com", "sub2.example.com", null));
         assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder(uri, listener())
+                     () -> c.newWebSocketBuilder()
+                             .subprotocols("sub1.example.com", null, "sub3.example.com"));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
                             .connectTimeout(null));
     }
 
     @Test(dataProvider = "badURIs")
-    void illegalURI(String u) {
-        assertThrows(IllegalArgumentException.class,
-                () -> HttpClient.newHttpClient()
-                                .newWebSocketBuilder(URI.create(u), listener()));
+    void illegalURI(URI uri) {
+        WebSocket.Builder b = HttpClient.newHttpClient().newWebSocketBuilder();
+        assertCompletesExceptionally(IllegalArgumentException.class,
+                                     b.buildAsync(uri, listener()));
     }
 
     @Test
     public void illegalHeaders() {
-        List<String> headers = List.of("Sec-WebSocket-Accept",
-                                       "Sec-WebSocket-Extensions",
-                                       "Sec-WebSocket-Key",
-                                       "Sec-WebSocket-Protocol",
-                                       "Sec-WebSocket-Version").stream()
-                .map(String::new).collect(Collectors.toList());
+        List<String> headers =
+                List.of("Sec-WebSocket-Accept",
+                        "Sec-WebSocket-Extensions",
+                        "Sec-WebSocket-Key",
+                        "Sec-WebSocket-Protocol",
+                        "Sec-WebSocket-Version")
+                        .stream()
+                        .flatMap(s -> Stream.of(s, new String(s))) // a string and a copy of it
+                        .collect(Collectors.toList());
 
         Function<String, CompletionStage<?>> f =
-                header -> HttpClient
-                        .newHttpClient()
-                        .newWebSocketBuilder(URI.create("ws://websocket.example.com"),
-                                             listener())
+                header -> HttpClient.newHttpClient()
+                        .newWebSocketBuilder()
                         .header(header, "value")
-                        .buildAsync();
+                        .buildAsync(VALID_URI, listener());
 
         headers.forEach(h -> assertCompletesExceptionally(IllegalArgumentException.class, f.apply(h)));
     }
@@ -120,38 +127,38 @@
     @Test(dataProvider = "badSubprotocols")
     public void illegalSubprotocolsSyntax(String s) {
         WebSocket.Builder b = HttpClient.newHttpClient()
-                .newWebSocketBuilder(URI.create("ws://websocket.example.com"),
-                                     listener());
-        b.subprotocols(s);
-        assertCompletesExceptionally(IllegalArgumentException.class, b.buildAsync());
+                .newWebSocketBuilder()
+                .subprotocols(s);
+        assertCompletesExceptionally(IllegalArgumentException.class,
+                                     b.buildAsync(VALID_URI, listener()));
     }
 
     @Test(dataProvider = "duplicatingSubprotocols")
     public void illegalSubprotocolsDuplicates(String mostPreferred,
                                               String[] lesserPreferred) {
         WebSocket.Builder b = HttpClient.newHttpClient()
-                .newWebSocketBuilder(URI.create("ws://websocket.example.com"),
-                                     listener());
-        b.subprotocols(mostPreferred, lesserPreferred);
-        assertCompletesExceptionally(IllegalArgumentException.class, b.buildAsync());
+                .newWebSocketBuilder()
+                .subprotocols(mostPreferred, lesserPreferred);
+        assertCompletesExceptionally(IllegalArgumentException.class,
+                                     b.buildAsync(VALID_URI, listener()));
     }
 
     @Test(dataProvider = "badConnectTimeouts")
     public void illegalConnectTimeout(Duration d) {
         WebSocket.Builder b = HttpClient.newHttpClient()
-                .newWebSocketBuilder(URI.create("ws://websocket.example.com"),
-                                     listener());
-        b.connectTimeout(d);
-        assertCompletesExceptionally(IllegalArgumentException.class, b.buildAsync());
+                .newWebSocketBuilder()
+                .connectTimeout(d);
+        assertCompletesExceptionally(IllegalArgumentException.class,
+                                     b.buildAsync(VALID_URI, listener()));
     }
 
     @DataProvider
     public Object[][] badURIs() {
         return new Object[][]{
-                {"http://example.com"},
-                {"ftp://example.com"},
-                {"wss://websocket.example.com/hello#fragment"},
-                {"ws://websocket.example.com/hello#fragment"},
+                {URI.create("http://example.com")},
+                {URI.create("ftp://example.com")},
+                {URI.create("wss://websocket.example.com/hello#fragment")},
+                {URI.create("ws://websocket.example.com/hello#fragment")},
         };
     }
 
@@ -179,6 +186,7 @@
     @DataProvider
     public static Object[][] badSubprotocols() {
         return new Object[][]{
+                {""},
                 {new String("")},
                 {"round-brackets("},
                 {"round-brackets)"},
--- a/test/jdk/java/net/httpclient/websocket/security/WSURLPermissionTest.java	Fri Nov 17 18:38:05 2017 +0000
+++ b/test/jdk/java/net/httpclient/websocket/security/WSURLPermissionTest.java	Sat Nov 18 20:13:09 2017 +0300
@@ -96,151 +96,151 @@
         HttpClient noProxyClient = HttpClient.newHttpClient();
         return new Object[][]{
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                       // no actions
               new URLPermission[] { new URLPermission(wsURI.toString()) },
               "0"  /* for log file identification */ },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                       // scheme wildcard
               new URLPermission[] { new URLPermission("ws://*") },
               "0.1" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                       // port wildcard
               new URLPermission[] { new URLPermission("ws://"+wsURI.getHost()+":*") },
               "0.2" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                        // empty actions
               new URLPermission[] { new URLPermission(wsURI.toString(), "") },
               "1" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                         // colon
               new URLPermission[] { new URLPermission(wsURI.toString(), ":") },
               "2" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                        // wildcard
               new URLPermission[] { new URLPermission(wsURI.toString(), "*:*") },
               "3" },
 
             // WS permission checking is agnostic of method, any/none will do
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                        // specific method
               new URLPermission[] { new URLPermission(wsURI.toString(), "GET") },
               "3.1" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                        // specific method
               new URLPermission[] { new URLPermission(wsURI.toString(), "POST") },
               "3.2" },
 
             { (PrivilegedExceptionAction<?>)() -> {
                 URI uriWithPath = wsURI.resolve("/path/x");
-                 noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(uriWithPath, noOpListener).get().abort();
                  return null; },                                       // path
               new URLPermission[] { new URLPermission(wsURI.resolve("/path/x").toString()) },
               "4" },
 
             { (PrivilegedExceptionAction<?>)() -> {
                 URI uriWithPath = wsURI.resolve("/path/x");
-                 noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(uriWithPath, noOpListener).get().abort();
                  return null; },                                       // same dir wildcard
               new URLPermission[] { new URLPermission(wsURI.resolve("/path/*").toString()) },
               "5" },
 
             { (PrivilegedExceptionAction<?>)() -> {
                 URI uriWithPath = wsURI.resolve("/path/x");
-                 noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(uriWithPath, noOpListener).get().abort();
                  return null; },                                       // recursive
               new URLPermission[] { new URLPermission(wsURI.resolve("/path/-").toString()) },
               "6" },
 
             { (PrivilegedExceptionAction<?>)() -> {
                 URI uriWithPath = wsURI.resolve("/path/x");
-                 noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(uriWithPath, noOpListener).get().abort();
                  return null; },                                       // recursive top
               new URLPermission[] { new URLPermission(wsURI.resolve("/-").toString()) },
               "7" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+                 noProxyClient.newWebSocketBuilder()
                               .header("A-Header", "A-Value")  // header
-                              .buildAsync().get().abort();
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },
               new URLPermission[] { new URLPermission(wsURI.toString(), ":A-Header") },
               "8" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+                 noProxyClient.newWebSocketBuilder()
                               .header("A-Header", "A-Value")  // header
-                              .buildAsync().get().abort();
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                        // wildcard
               new URLPermission[] { new URLPermission(wsURI.toString(), ":*") },
               "9" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+                 noProxyClient.newWebSocketBuilder()
                               .header("A-Header", "A-Value")  // headers
                               .header("B-Header", "B-Value")  // headers
-                              .buildAsync().get().abort();
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },
               new URLPermission[] { new URLPermission(wsURI.toString(), ":A-Header,B-Header") },
               "10" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+                 noProxyClient.newWebSocketBuilder()
                               .header("A-Header", "A-Value")  // headers
                               .header("B-Header", "B-Value")  // headers
-                              .buildAsync().get().abort();
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                        // wildcard
               new URLPermission[] { new URLPermission(wsURI.toString(), ":*") },
               "11" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+                 noProxyClient.newWebSocketBuilder()
                               .header("A-Header", "A-Value")  // headers
                               .header("B-Header", "B-Value")  // headers
-                              .buildAsync().get().abort();
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                        // wildcards
               new URLPermission[] { new URLPermission(wsURI.toString(), "*:*") },
               "12" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+                 noProxyClient.newWebSocketBuilder()
                               .header("A-Header", "A-Value")  // multi-value
                               .header("A-Header", "B-Value")  // headers
-                              .buildAsync().get().abort();
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                        // wildcard
               new URLPermission[] { new URLPermission(wsURI.toString(), ":*") },
               "13" },
 
             { (PrivilegedExceptionAction<?>)() -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+                 noProxyClient.newWebSocketBuilder()
                               .header("A-Header", "A-Value")  // multi-value
                               .header("A-Header", "B-Value")  // headers
-                              .buildAsync().get().abort();
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                        // single grant
               new URLPermission[] { new URLPermission(wsURI.toString(), ":A-Header") },
               "14" },
@@ -249,8 +249,8 @@
             { (PrivilegedExceptionAction<?>)() -> {
                  ProxySelector ps = ProxySelector.of(null);
                  HttpClient client = HttpClient.newBuilder().proxy(ps).build();
-                 client.newWebSocketBuilder(wsURI, noOpListener)
-                       .buildAsync().get().abort();
+                 client.newWebSocketBuilder()
+                       .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },
               new URLPermission[] { new URLPermission(wsURI.toString()) },
               "15" },
@@ -264,8 +264,8 @@
                      public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { }
                  };
                  HttpClient client = HttpClient.newBuilder().proxy(ps).build();
-                 client.newWebSocketBuilder(wsURI, noOpListener)
-                       .buildAsync().get().abort();
+                 client.newWebSocketBuilder()
+                       .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },
               new URLPermission[] { new URLPermission(wsURI.toString()) },
               "16" },
@@ -275,8 +275,8 @@
                  assert proxyAddress != null;
                  ProxySelector ps = ProxySelector.of(proxyAddress);
                  HttpClient client = HttpClient.newBuilder().proxy(ps).build();
-                 client.newWebSocketBuilder(wsURI, noOpListener)
-                       .buildAsync().get().abort();
+                 client.newWebSocketBuilder()
+                       .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },
               new URLPermission[] {
                     new URLPermission(wsURI.toString()),            // CONNECT action string
@@ -288,8 +288,8 @@
                  assert proxyAddress != null;
                  ProxySelector ps = ProxySelector.of(proxyAddress);
                  HttpClient client = HttpClient.newBuilder().proxy(ps).build();
-                 client.newWebSocketBuilder(wsURI, noOpListener)
-                       .buildAsync().get().abort();
+                 client.newWebSocketBuilder()
+                       .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },
               new URLPermission[] {
                     new URLPermission(wsURI.toString()),            // no action string
@@ -301,8 +301,8 @@
                  assert proxyAddress != null;
                  ProxySelector ps = ProxySelector.of(proxyAddress);
                  HttpClient client = HttpClient.newBuilder().proxy(ps).build();
-                 client.newWebSocketBuilder(wsURI, noOpListener)
-                       .buildAsync().get().abort();
+                 client.newWebSocketBuilder()
+                       .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },
               new URLPermission[] {
                     new URLPermission(wsURI.toString()),            // wildcard headers
@@ -314,8 +314,8 @@
                  assert proxyAddress != null;
                  CountingProxySelector ps = CountingProxySelector.of(proxyAddress);
                  HttpClient client = HttpClient.newBuilder().proxy(ps).build();
-                 client.newWebSocketBuilder(wsURI, noOpListener)
-                       .buildAsync().get().abort();
+                 client.newWebSocketBuilder()
+                       .buildAsync(wsURI, noOpListener).get().abort();
                  assertEquals(ps.count(), 1);  // ps.select only invoked once
                  return null; },
               new URLPermission[] {
@@ -328,8 +328,8 @@
                  assert proxyAddress != null;
                  ProxySelector ps = ProxySelector.of(proxyAddress);
                  HttpClient client = HttpClient.newBuilder().proxy(ps).build();
-                 client.newWebSocketBuilder(wsURI, noOpListener)
-                       .buildAsync().get().abort();
+                 client.newWebSocketBuilder()
+                       .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },
               new URLPermission[] {
                     new URLPermission(wsURI.toString()),
@@ -340,8 +340,8 @@
                  assert proxyAddress != null;
                  ProxySelector ps = ProxySelector.of(proxyAddress);
                  HttpClient client = HttpClient.newBuilder().proxy(ps).build();
-                 client.newWebSocketBuilder(wsURI, noOpListener)
-                       .buildAsync().get().abort();
+                 client.newWebSocketBuilder()
+                       .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },
               new URLPermission[] {
                     new URLPermission("ws://*"),                    // wildcard ws URL
@@ -419,59 +419,59 @@
         HttpClient noProxyClient = HttpClient.newHttpClient();
         return new Object[][]{
             { (PrivilegedExceptionAction<?>) () -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null;
               },
               new URLPermission[]{ /* no permissions */ },
               "50"  /* for log file identification */},
 
             { (PrivilegedExceptionAction<?>) () -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null;
               },                                        // wrong scheme
               new URLPermission[]{ new URLPermission("http://*") },
               "51" },
 
             { (PrivilegedExceptionAction<?>) () -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null;
               },                                        // wrong scheme
               new URLPermission[]{ new URLPermission("socket://*") },
               "52" },
 
             { (PrivilegedExceptionAction<?>) () -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null;
               },                                        // wrong host
               new URLPermission[]{ new URLPermission("ws://foo.com/") },
               "53" },
 
             { (PrivilegedExceptionAction<?>) () -> {
-                 noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(wsURI, noOpListener).get().abort();
                  return null;
               },                                        // wrong port
               new URLPermission[]{ new URLPermission("ws://"+ wsURI.getHost()+":5") },
               "54" },
 
             { (PrivilegedExceptionAction<?>) () -> {
-                  noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+                  noProxyClient.newWebSocketBuilder()
                                .header("A-Header", "A-Value")
-                               .buildAsync().get().abort();
+                               .buildAsync(wsURI, noOpListener).get().abort();
                   return null;
               },                                                    // only perm to set B not A
               new URLPermission[] { new URLPermission(wsURI.toString(), "*:B-Header") },
               "55" },
 
             { (PrivilegedExceptionAction<?>) () -> {
-                  noProxyClient.newWebSocketBuilder(wsURI, noOpListener)
+                  noProxyClient.newWebSocketBuilder()
                                .header("A-Header", "A-Value")
                                .header("B-Header", "B-Value")
-                               .buildAsync().get().abort();
+                               .buildAsync(wsURI, noOpListener).get().abort();
                   return null;
               },                                                    // only perm to set B not A
               new URLPermission[] { new URLPermission(wsURI.toString(), "*:B-Header") },
@@ -479,16 +479,16 @@
 
             { (PrivilegedExceptionAction<?>)() -> {
                 URI uriWithPath = wsURI.resolve("/path/x");
-                 noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(uriWithPath, noOpListener).get().abort();
                  return null; },                                    // wrong path
               new URLPermission[] { new URLPermission(wsURI.resolve("/aDiffPath/").toString()) },
               "57" },
 
             { (PrivilegedExceptionAction<?>)() -> {
                 URI uriWithPath = wsURI.resolve("/path/x");
-                 noProxyClient.newWebSocketBuilder(uriWithPath, noOpListener)
-                              .buildAsync().get().abort();
+                 noProxyClient.newWebSocketBuilder()
+                              .buildAsync(uriWithPath, noOpListener).get().abort();
                  return null; },                                    // more specific path
               new URLPermission[] { new URLPermission(wsURI.resolve("/path/x/y").toString()) },
               "58" },
@@ -498,8 +498,8 @@
                  assert proxyAddress != null;
                  ProxySelector ps = ProxySelector.of(proxyAddress);
                  HttpClient client = HttpClient.newBuilder().proxy(ps).build();
-                 client.newWebSocketBuilder(wsURI, noOpListener)
-                       .buildAsync().get().abort();
+                 client.newWebSocketBuilder()
+                       .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },                                    // missing proxy perm
               new URLPermission[] { new URLPermission(wsURI.toString()) },
               "100" },
@@ -509,8 +509,8 @@
                  assert proxyAddress != null;
                  ProxySelector ps = ProxySelector.of(proxyAddress);
                  HttpClient client = HttpClient.newBuilder().proxy(ps).build();
-                 client.newWebSocketBuilder(wsURI, noOpListener)
-                       .buildAsync().get().abort();
+                 client.newWebSocketBuilder()
+                       .buildAsync(wsURI, noOpListener).get().abort();
                  return null; },
               new URLPermission[] {
                     new URLPermission(wsURI.toString()),            // missing proxy CONNECT