< prev index next >
   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.Locale;
  36 import java.util.Map;
  37 import java.util.Set;
  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 WebSocketBuilderImpl implements WebSocket.Builder {
  45 
  46     private static final Set<String> FORBIDDEN_HEADERS_LOWER_CASED = Set.of(
  47             "connection", "upgrade", "sec-websocket-accept", "sec-websocket-extensions",
  48             "sec-websocket-key", "sec-websocket-protocol", "sec-websocket-version");
  49 
  50     private final URI uri;
  51     private final HttpClient client;
  52     private final LinkedHashMap<String, List<String>> headers = new LinkedHashMap<>();
  53     private final WebSocket.Listener listener;
  54     private Collection<String> subprotocols = Collections.emptyList();
  55     private long timeout;
  56     private TimeUnit timeUnit;
  57 
  58     WebSocketBuilderImpl(URI uri, HttpClient client, WebSocket.Listener listener) {
  59         requireNonNull(uri, "uri");
  60         requireNonNull(client, "client");
  61         requireNonNull(listener, "listener");
  62         checkUri(uri);
  63         this.uri = uri;
  64         this.listener = listener;
  65         this.client = client;
  66     }
  67 
  68     @Override
  69     public WebSocket.Builder header(String name, String value) {
  70         requireNonNull(name, "name");
  71         requireNonNull(value, "value");
  72         // TODO: I don't like the fact the knowledge about
  73         // case-insensitivity, order and name->list(value) structure, etc.
  74         // sits is in the builder
  75         if (FORBIDDEN_HEADERS_LOWER_CASED.contains(name.toLowerCase(Locale.ROOT))) {
  76             throw new IllegalArgumentException(
  77                     format("Header '%s' is used in the WebSocket Protocol", name));
  78         }
  79         List<String> values = headers.computeIfAbsent(name, n -> new LinkedList<>());
  80         values.add(value);
  81         return this;
  82     }
  83 
  84     @Override
  85     public WebSocket.Builder subprotocols(String mostPreferred, String... lesserPreferred) {
  86         requireNonNull(mostPreferred, "mostPreferred");
  87         requireNonNull(lesserPreferred, "lesserPreferred");
  88         this.subprotocols = checkAndReturnSubprotocols(mostPreferred, lesserPreferred);
  89         return this;
  90     }
  91 
  92     @Override
  93     public WebSocket.Builder connectTimeout(long timeout, TimeUnit unit) {
  94         if (timeout < 0) {
  95             throw new IllegalArgumentException("Negative timeout: " + timeout);
  96         }
  97         requireNonNull(unit, "unit");
  98         this.timeout = timeout;
  99         this.timeUnit = unit;
 100         return this;
 101     }
 102 
 103     @Override
 104     public CompletableFuture<WebSocket> buildAsync() {
 105         return WebSocketImpl.newInstanceAsync(this);
 106     }
 107 
 108     private static void checkUri(URI uri) {
 109         String s = uri.getScheme();
 110         if (!("ws".equalsIgnoreCase(s) || "wss".equalsIgnoreCase(s))) {
 111             throw new IllegalArgumentException
 112                     ("URI scheme not ws or wss (RFC 6455 3.): " + s);
 113         }
 114         String fragment = uri.getFragment();
 115         if (fragment != null) {
 116             throw new IllegalArgumentException(format
 117                     ("Fragment not allowed in a WebSocket URI (RFC 6455 3.): '%s'",
 118                             fragment));
 119         }
 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     long getTimeout() { return timeout; }
 139 
 140     TimeUnit getTimeUnit() { return timeUnit; }
 141 
 142     private static Collection<String> checkAndReturnSubprotocols(String mostPreferred,
 143                                                                  String... lesserPreferred) {
 144         checkSubprotocolSyntax(mostPreferred, "mostPreferred");
 145         LinkedHashSet<String> subprotocols = new LinkedHashSet<>(1 + lesserPreferred.length);
 146         subprotocols.add(mostPreferred);
 147         for (int i = 0; i < lesserPreferred.length; i++) {
 148             String p = lesserPreferred[i];
 149             String location = format("lesserPreferred[%s]", i);
 150             requireNonNull(p, location);
 151             checkSubprotocolSyntax(p, location);
 152             if (!subprotocols.add(p)) {
 153                 throw new IllegalArgumentException(format(
 154                         "Duplicate subprotocols (RFC 6455 4.1.): '%s'", p));
 155             }
 156         }
 157         return subprotocols;
 158     }
 159 
 160     private static void checkSubprotocolSyntax(String subprotocol, String location) {
 161         if (subprotocol.isEmpty()) {
 162             throw new IllegalArgumentException
 163                     ("Subprotocol name is empty (RFC 6455 4.1.): " + location);
 164         }
 165         if (!subprotocol.chars().allMatch(c -> 0x21 <= c && c <= 0x7e)) {
 166             throw new IllegalArgumentException
 167                     ("Subprotocol name contains illegal characters (RFC 6455 4.1.): "
 168                             + location);
 169         }
 170     }
 171 }
< prev index next >