jdk/src/java.httpclient/share/classes/java/net/http/WSBuilder.java
changeset 42483 3850c235c3fb
parent 42482 15297dde0d55
parent 42479 a80dbf731cbe
child 42489 a9e4de33da2e
equal deleted inserted replaced
42482:15297dde0d55 42483:3850c235c3fb
     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 
       
    26 package java.net.http;
       
    27 
       
    28 import java.net.URI;
       
    29 import java.time.Duration;
       
    30 import java.util.ArrayList;
       
    31 import java.util.Collection;
       
    32 import java.util.Collections;
       
    33 import java.util.LinkedHashMap;
       
    34 import java.util.LinkedHashSet;
       
    35 import java.util.LinkedList;
       
    36 import java.util.List;
       
    37 import java.util.Map;
       
    38 import java.util.Set;
       
    39 import java.util.TreeSet;
       
    40 import java.util.concurrent.CompletableFuture;
       
    41 import java.util.concurrent.TimeUnit;
       
    42 
       
    43 import static java.lang.String.format;
       
    44 import static java.util.Objects.requireNonNull;
       
    45 
       
    46 final class WSBuilder implements WebSocket.Builder {
       
    47 
       
    48     private static final Set<String> FORBIDDEN_HEADERS =
       
    49             new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
       
    50 
       
    51     static {
       
    52         List<String> headers = List.of("Connection", "Upgrade",
       
    53                 "Sec-WebSocket-Accept", "Sec-WebSocket-Extensions",
       
    54                 "Sec-WebSocket-Key", "Sec-WebSocket-Protocol",
       
    55                 "Sec-WebSocket-Version");
       
    56         FORBIDDEN_HEADERS.addAll(headers);
       
    57     }
       
    58 
       
    59     private final URI uri;
       
    60     private final HttpClient client;
       
    61     private final LinkedHashMap<String, List<String>> headers = new LinkedHashMap<>();
       
    62     private final WebSocket.Listener listener;
       
    63     private Collection<String> subprotocols = Collections.emptyList();
       
    64     private Duration timeout;
       
    65 
       
    66     WSBuilder(URI uri, HttpClient client, WebSocket.Listener listener) {
       
    67         checkURI(requireNonNull(uri, "uri"));
       
    68         requireNonNull(client, "client");
       
    69         requireNonNull(listener, "listener");
       
    70         this.uri = uri;
       
    71         this.listener = listener;
       
    72         this.client = client;
       
    73     }
       
    74 
       
    75     @Override
       
    76     public WebSocket.Builder header(String name, String value) {
       
    77         requireNonNull(name, "name");
       
    78         requireNonNull(value, "value");
       
    79         if (FORBIDDEN_HEADERS.contains(name)) {
       
    80             throw new IllegalArgumentException(
       
    81                     format("Header '%s' is used in the WebSocket Protocol", name));
       
    82         }
       
    83         List<String> values = headers.computeIfAbsent(name, n -> new LinkedList<>());
       
    84         values.add(value);
       
    85         return this;
       
    86     }
       
    87 
       
    88     @Override
       
    89     public WebSocket.Builder subprotocols(String mostPreferred, String... lesserPreferred) {
       
    90         requireNonNull(mostPreferred, "mostPreferred");
       
    91         requireNonNull(lesserPreferred, "lesserPreferred");
       
    92         this.subprotocols = checkSubprotocols(mostPreferred, lesserPreferred);
       
    93         return this;
       
    94     }
       
    95 
       
    96     @Override
       
    97     public WebSocket.Builder connectTimeout(Duration timeout) {
       
    98         this.timeout = requireNonNull(timeout, "timeout");
       
    99         return this;
       
   100     }
       
   101 
       
   102     @Override
       
   103     public CompletableFuture<WebSocket> buildAsync() {
       
   104         return WS.newInstanceAsync(this);
       
   105     }
       
   106 
       
   107     private static URI checkURI(URI uri) {
       
   108         String s = uri.getScheme();
       
   109         if (!("ws".equalsIgnoreCase(s) || "wss".equalsIgnoreCase(s))) {
       
   110             throw new IllegalArgumentException
       
   111                     ("URI scheme not ws or wss (RFC 6455 3.): " + s);
       
   112         }
       
   113         String fragment = uri.getFragment();
       
   114         if (fragment != null) {
       
   115             throw new IllegalArgumentException(format
       
   116                     ("Fragment not allowed in a WebSocket URI (RFC 6455 3.): '%s'",
       
   117                             fragment));
       
   118         }
       
   119         return uri;
       
   120     }
       
   121 
       
   122     URI getUri() { return uri; }
       
   123 
       
   124     HttpClient getClient() { return client; }
       
   125 
       
   126     Map<String, List<String>> getHeaders() {
       
   127         LinkedHashMap<String, List<String>> copy = new LinkedHashMap<>(headers.size());
       
   128         headers.forEach((name, values) -> copy.put(name, new LinkedList<>(values)));
       
   129         return copy;
       
   130     }
       
   131 
       
   132     WebSocket.Listener getListener() { return listener; }
       
   133 
       
   134     Collection<String> getSubprotocols() {
       
   135         return new ArrayList<>(subprotocols);
       
   136     }
       
   137 
       
   138     Duration getConnectTimeout() { return timeout; }
       
   139 
       
   140     private static Collection<String> checkSubprotocols(String mostPreferred,
       
   141                                                         String... lesserPreferred) {
       
   142         checkSubprotocolSyntax(mostPreferred, "mostPreferred");
       
   143         LinkedHashSet<String> sp = new LinkedHashSet<>(1 + lesserPreferred.length);
       
   144         sp.add(mostPreferred);
       
   145         for (int i = 0; i < lesserPreferred.length; i++) {
       
   146             String p = lesserPreferred[i];
       
   147             String location = format("lesserPreferred[%s]", i);
       
   148             requireNonNull(p, location);
       
   149             checkSubprotocolSyntax(p, location);
       
   150             if (!sp.add(p)) {
       
   151                 throw new IllegalArgumentException(format(
       
   152                         "Duplicate subprotocols (RFC 6455 4.1.): '%s'", p));
       
   153             }
       
   154         }
       
   155         return sp;
       
   156     }
       
   157 
       
   158     private static void checkSubprotocolSyntax(String subprotocol, String location) {
       
   159         if (subprotocol.isEmpty()) {
       
   160             throw new IllegalArgumentException
       
   161                     ("Subprotocol name is empty (RFC 6455 4.1.): " + location);
       
   162         }
       
   163         if (!subprotocol.chars().allMatch(c -> 0x21 <= c && c <= 0x7e)) {
       
   164             throw new IllegalArgumentException
       
   165                     ("Subprotocol name contains illegal characters (RFC 6455 4.1.): "
       
   166                             + location);
       
   167         }
       
   168     }
       
   169 }