< 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 >