jdk/src/java.httpclient/share/classes/java/net/http/WSBuilder.java
changeset 37874 02589df0999a
child 39133 b5641ce64cf7
equal deleted inserted replaced
37858:7c04fcb12bd4 37874:02589df0999a
       
     1 /*
       
     2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package java.net.http;
       
    26 
       
    27 import java.net.URI;
       
    28 import java.util.ArrayList;
       
    29 import java.util.Collection;
       
    30 import java.util.Collections;
       
    31 import java.util.LinkedHashMap;
       
    32 import java.util.LinkedHashSet;
       
    33 import java.util.LinkedList;
       
    34 import java.util.List;
       
    35 import java.util.Map;
       
    36 import java.util.Set;
       
    37 import java.util.TreeSet;
       
    38 import java.util.concurrent.CompletableFuture;
       
    39 import java.util.concurrent.TimeUnit;
       
    40 
       
    41 import static java.lang.String.format;
       
    42 import static java.util.Objects.requireNonNull;
       
    43 
       
    44 final class WSBuilder implements WebSocket.Builder {
       
    45 
       
    46     private static final Set<String> FORBIDDEN_HEADERS =
       
    47             new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
       
    48 
       
    49     static {
       
    50         List<String> headers = List.of("Connection", "Upgrade",
       
    51                 "Sec-WebSocket-Accept", "Sec-WebSocket-Extensions",
       
    52                 "Sec-WebSocket-Key", "Sec-WebSocket-Protocol",
       
    53                 "Sec-WebSocket-Version");
       
    54         FORBIDDEN_HEADERS.addAll(headers);
       
    55     }
       
    56 
       
    57     private final URI uri;
       
    58     private final HttpClient client;
       
    59     private final LinkedHashMap<String, List<String>> headers = new LinkedHashMap<>();
       
    60     private final WebSocket.Listener listener;
       
    61     private Collection<String> subprotocols = Collections.emptyList();
       
    62     private long timeout;
       
    63     private TimeUnit timeUnit;
       
    64 
       
    65     WSBuilder(URI uri, HttpClient client, WebSocket.Listener listener) {
       
    66         checkURI(requireNonNull(uri, "uri"));
       
    67         requireNonNull(client, "client");
       
    68         requireNonNull(listener, "listener");
       
    69         this.uri = uri;
       
    70         this.listener = listener;
       
    71         this.client = client;
       
    72     }
       
    73 
       
    74     @Override
       
    75     public WebSocket.Builder header(String name, String value) {
       
    76         requireNonNull(name, "name");
       
    77         requireNonNull(value, "value");
       
    78         if (FORBIDDEN_HEADERS.contains(name)) {
       
    79             throw new IllegalArgumentException(
       
    80                     format("Header '%s' is used in the WebSocket Protocol", name));
       
    81         }
       
    82         List<String> values = headers.computeIfAbsent(name, n -> new LinkedList<>());
       
    83         values.add(value);
       
    84         return this;
       
    85     }
       
    86 
       
    87     @Override
       
    88     public WebSocket.Builder subprotocols(String mostPreferred, String... lesserPreferred) {
       
    89         requireNonNull(mostPreferred, "mostPreferred");
       
    90         requireNonNull(lesserPreferred, "lesserPreferred");
       
    91         this.subprotocols = checkSubprotocols(mostPreferred, lesserPreferred);
       
    92         return this;
       
    93     }
       
    94 
       
    95     @Override
       
    96     public WebSocket.Builder connectTimeout(long timeout, TimeUnit unit) {
       
    97         if (timeout < 0) {
       
    98             throw new IllegalArgumentException("Negative timeout: " + timeout);
       
    99         }
       
   100         requireNonNull(unit, "unit");
       
   101         this.timeout = timeout;
       
   102         this.timeUnit = unit;
       
   103         return this;
       
   104     }
       
   105 
       
   106     @Override
       
   107     public CompletableFuture<WebSocket> buildAsync() {
       
   108         return WS.newInstanceAsync(this);
       
   109     }
       
   110 
       
   111     private static URI checkURI(URI uri) {
       
   112         String s = uri.getScheme();
       
   113         if (!("ws".equalsIgnoreCase(s) || "wss".equalsIgnoreCase(s))) {
       
   114             throw new IllegalArgumentException
       
   115                     ("URI scheme not ws or wss (RFC 6455 3.): " + s);
       
   116         }
       
   117         String fragment = uri.getFragment();
       
   118         if (fragment != null) {
       
   119             throw new IllegalArgumentException(format
       
   120                     ("Fragment not allowed in a WebSocket URI (RFC 6455 3.): '%s'",
       
   121                             fragment));
       
   122         }
       
   123         return uri;
       
   124     }
       
   125 
       
   126     URI getUri() { return uri; }
       
   127 
       
   128     HttpClient getClient() { return client; }
       
   129 
       
   130     Map<String, List<String>> getHeaders() {
       
   131         LinkedHashMap<String, List<String>> copy = new LinkedHashMap<>(headers.size());
       
   132         headers.forEach((name, values) -> copy.put(name, new LinkedList<>(values)));
       
   133         return copy;
       
   134     }
       
   135 
       
   136     WebSocket.Listener getListener() { return listener; }
       
   137 
       
   138     Collection<String> getSubprotocols() {
       
   139         return new ArrayList<>(subprotocols);
       
   140     }
       
   141 
       
   142     long getTimeout() { return timeout; }
       
   143 
       
   144     TimeUnit getTimeUnit() { return timeUnit; }
       
   145 
       
   146     private static Collection<String> checkSubprotocols(String mostPreferred,
       
   147                                                         String... lesserPreferred) {
       
   148         checkSubprotocolSyntax(mostPreferred, "mostPreferred");
       
   149         LinkedHashSet<String> sp = new LinkedHashSet<>(1 + lesserPreferred.length);
       
   150         sp.add(mostPreferred);
       
   151         for (int i = 0; i < lesserPreferred.length; i++) {
       
   152             String p = lesserPreferred[i];
       
   153             String location = format("lesserPreferred[%s]", i);
       
   154             requireNonNull(p, location);
       
   155             checkSubprotocolSyntax(p, location);
       
   156             if (!sp.add(p)) {
       
   157                 throw new IllegalArgumentException(format(
       
   158                         "Duplicate subprotocols (RFC 6455 4.1.): '%s'", p));
       
   159             }
       
   160         }
       
   161         return sp;
       
   162     }
       
   163 
       
   164     private static void checkSubprotocolSyntax(String subprotocol, String location) {
       
   165         if (subprotocol.isEmpty()) {
       
   166             throw new IllegalArgumentException
       
   167                     ("Subprotocol name is empty (RFC 6455 4.1.): " + location);
       
   168         }
       
   169         if (!subprotocol.chars().allMatch(c -> 0x21 <= c && c <= 0x7e)) {
       
   170             throw new IllegalArgumentException
       
   171                     ("Subprotocol name contains illegal characters (RFC 6455 4.1.): "
       
   172                             + location);
       
   173         }
       
   174     }
       
   175 }