xref: /unit/src/java/nginx/unit/websocket/WsWebSocketContainer.java (revision 1157:7ae152bda303)
1*1157Smax.romanov@nginx.com /*
2*1157Smax.romanov@nginx.com  *  Licensed to the Apache Software Foundation (ASF) under one or more
3*1157Smax.romanov@nginx.com  *  contributor license agreements.  See the NOTICE file distributed with
4*1157Smax.romanov@nginx.com  *  this work for additional information regarding copyright ownership.
5*1157Smax.romanov@nginx.com  *  The ASF licenses this file to You under the Apache License, Version 2.0
6*1157Smax.romanov@nginx.com  *  (the "License"); you may not use this file except in compliance with
7*1157Smax.romanov@nginx.com  *  the License.  You may obtain a copy of the License at
8*1157Smax.romanov@nginx.com  *
9*1157Smax.romanov@nginx.com  *      http://www.apache.org/licenses/LICENSE-2.0
10*1157Smax.romanov@nginx.com  *
11*1157Smax.romanov@nginx.com  *  Unless required by applicable law or agreed to in writing, software
12*1157Smax.romanov@nginx.com  *  distributed under the License is distributed on an "AS IS" BASIS,
13*1157Smax.romanov@nginx.com  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*1157Smax.romanov@nginx.com  *  See the License for the specific language governing permissions and
15*1157Smax.romanov@nginx.com  *  limitations under the License.
16*1157Smax.romanov@nginx.com  */
17*1157Smax.romanov@nginx.com package nginx.unit.websocket;
18*1157Smax.romanov@nginx.com 
19*1157Smax.romanov@nginx.com import java.io.EOFException;
20*1157Smax.romanov@nginx.com import java.io.File;
21*1157Smax.romanov@nginx.com import java.io.FileInputStream;
22*1157Smax.romanov@nginx.com import java.io.IOException;
23*1157Smax.romanov@nginx.com import java.io.InputStream;
24*1157Smax.romanov@nginx.com import java.net.InetSocketAddress;
25*1157Smax.romanov@nginx.com import java.net.Proxy;
26*1157Smax.romanov@nginx.com import java.net.ProxySelector;
27*1157Smax.romanov@nginx.com import java.net.SocketAddress;
28*1157Smax.romanov@nginx.com import java.net.URI;
29*1157Smax.romanov@nginx.com import java.net.URISyntaxException;
30*1157Smax.romanov@nginx.com import java.nio.ByteBuffer;
31*1157Smax.romanov@nginx.com import java.nio.channels.AsynchronousChannelGroup;
32*1157Smax.romanov@nginx.com import java.nio.channels.AsynchronousSocketChannel;
33*1157Smax.romanov@nginx.com import java.nio.charset.StandardCharsets;
34*1157Smax.romanov@nginx.com import java.security.KeyStore;
35*1157Smax.romanov@nginx.com import java.util.ArrayList;
36*1157Smax.romanov@nginx.com import java.util.Arrays;
37*1157Smax.romanov@nginx.com import java.util.Collections;
38*1157Smax.romanov@nginx.com import java.util.HashMap;
39*1157Smax.romanov@nginx.com import java.util.HashSet;
40*1157Smax.romanov@nginx.com import java.util.List;
41*1157Smax.romanov@nginx.com import java.util.Locale;
42*1157Smax.romanov@nginx.com import java.util.Map;
43*1157Smax.romanov@nginx.com import java.util.Map.Entry;
44*1157Smax.romanov@nginx.com import java.util.Random;
45*1157Smax.romanov@nginx.com import java.util.Set;
46*1157Smax.romanov@nginx.com import java.util.concurrent.ConcurrentHashMap;
47*1157Smax.romanov@nginx.com import java.util.concurrent.ExecutionException;
48*1157Smax.romanov@nginx.com import java.util.concurrent.Future;
49*1157Smax.romanov@nginx.com import java.util.concurrent.TimeUnit;
50*1157Smax.romanov@nginx.com import java.util.concurrent.TimeoutException;
51*1157Smax.romanov@nginx.com 
52*1157Smax.romanov@nginx.com import javax.net.ssl.SSLContext;
53*1157Smax.romanov@nginx.com import javax.net.ssl.SSLEngine;
54*1157Smax.romanov@nginx.com import javax.net.ssl.SSLException;
55*1157Smax.romanov@nginx.com import javax.net.ssl.SSLParameters;
56*1157Smax.romanov@nginx.com import javax.net.ssl.TrustManagerFactory;
57*1157Smax.romanov@nginx.com import javax.websocket.ClientEndpoint;
58*1157Smax.romanov@nginx.com import javax.websocket.ClientEndpointConfig;
59*1157Smax.romanov@nginx.com import javax.websocket.CloseReason;
60*1157Smax.romanov@nginx.com import javax.websocket.CloseReason.CloseCodes;
61*1157Smax.romanov@nginx.com import javax.websocket.DeploymentException;
62*1157Smax.romanov@nginx.com import javax.websocket.Endpoint;
63*1157Smax.romanov@nginx.com import javax.websocket.Extension;
64*1157Smax.romanov@nginx.com import javax.websocket.HandshakeResponse;
65*1157Smax.romanov@nginx.com import javax.websocket.Session;
66*1157Smax.romanov@nginx.com import javax.websocket.WebSocketContainer;
67*1157Smax.romanov@nginx.com 
68*1157Smax.romanov@nginx.com import org.apache.juli.logging.Log;
69*1157Smax.romanov@nginx.com import org.apache.juli.logging.LogFactory;
70*1157Smax.romanov@nginx.com import org.apache.tomcat.InstanceManager;
71*1157Smax.romanov@nginx.com import org.apache.tomcat.util.buf.StringUtils;
72*1157Smax.romanov@nginx.com import org.apache.tomcat.util.codec.binary.Base64;
73*1157Smax.romanov@nginx.com import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap;
74*1157Smax.romanov@nginx.com import org.apache.tomcat.util.res.StringManager;
75*1157Smax.romanov@nginx.com import nginx.unit.websocket.pojo.PojoEndpointClient;
76*1157Smax.romanov@nginx.com 
77*1157Smax.romanov@nginx.com public class WsWebSocketContainer implements WebSocketContainer, BackgroundProcess {
78*1157Smax.romanov@nginx.com 
79*1157Smax.romanov@nginx.com     private static final StringManager sm = StringManager.getManager(WsWebSocketContainer.class);
80*1157Smax.romanov@nginx.com     private static final Random RANDOM = new Random();
81*1157Smax.romanov@nginx.com     private static final byte[] CRLF = new byte[] { 13, 10 };
82*1157Smax.romanov@nginx.com 
83*1157Smax.romanov@nginx.com     private static final byte[] GET_BYTES = "GET ".getBytes(StandardCharsets.ISO_8859_1);
84*1157Smax.romanov@nginx.com     private static final byte[] ROOT_URI_BYTES = "/".getBytes(StandardCharsets.ISO_8859_1);
85*1157Smax.romanov@nginx.com     private static final byte[] HTTP_VERSION_BYTES =
86*1157Smax.romanov@nginx.com             " HTTP/1.1\r\n".getBytes(StandardCharsets.ISO_8859_1);
87*1157Smax.romanov@nginx.com 
88*1157Smax.romanov@nginx.com     private volatile AsynchronousChannelGroup asynchronousChannelGroup = null;
89*1157Smax.romanov@nginx.com     private final Object asynchronousChannelGroupLock = new Object();
90*1157Smax.romanov@nginx.com 
91*1157Smax.romanov@nginx.com     private final Log log = LogFactory.getLog(WsWebSocketContainer.class); // must not be static
92*1157Smax.romanov@nginx.com     private final Map<Endpoint, Set<WsSession>> endpointSessionMap =
93*1157Smax.romanov@nginx.com             new HashMap<>();
94*1157Smax.romanov@nginx.com     private final Map<WsSession,WsSession> sessions = new ConcurrentHashMap<>();
95*1157Smax.romanov@nginx.com     private final Object endPointSessionMapLock = new Object();
96*1157Smax.romanov@nginx.com 
97*1157Smax.romanov@nginx.com     private long defaultAsyncTimeout = -1;
98*1157Smax.romanov@nginx.com     private int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
99*1157Smax.romanov@nginx.com     private int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
100*1157Smax.romanov@nginx.com     private volatile long defaultMaxSessionIdleTimeout = 0;
101*1157Smax.romanov@nginx.com     private int backgroundProcessCount = 0;
102*1157Smax.romanov@nginx.com     private int processPeriod = Constants.DEFAULT_PROCESS_PERIOD;
103*1157Smax.romanov@nginx.com 
104*1157Smax.romanov@nginx.com     private InstanceManager instanceManager;
105*1157Smax.romanov@nginx.com 
getInstanceManager()106*1157Smax.romanov@nginx.com     InstanceManager getInstanceManager() {
107*1157Smax.romanov@nginx.com         return instanceManager;
108*1157Smax.romanov@nginx.com     }
109*1157Smax.romanov@nginx.com 
setInstanceManager(InstanceManager instanceManager)110*1157Smax.romanov@nginx.com     protected void setInstanceManager(InstanceManager instanceManager) {
111*1157Smax.romanov@nginx.com         this.instanceManager = instanceManager;
112*1157Smax.romanov@nginx.com     }
113*1157Smax.romanov@nginx.com 
114*1157Smax.romanov@nginx.com     @Override
connectToServer(Object pojo, URI path)115*1157Smax.romanov@nginx.com     public Session connectToServer(Object pojo, URI path)
116*1157Smax.romanov@nginx.com             throws DeploymentException {
117*1157Smax.romanov@nginx.com 
118*1157Smax.romanov@nginx.com         ClientEndpoint annotation =
119*1157Smax.romanov@nginx.com                 pojo.getClass().getAnnotation(ClientEndpoint.class);
120*1157Smax.romanov@nginx.com         if (annotation == null) {
121*1157Smax.romanov@nginx.com             throw new DeploymentException(
122*1157Smax.romanov@nginx.com                     sm.getString("wsWebSocketContainer.missingAnnotation",
123*1157Smax.romanov@nginx.com                             pojo.getClass().getName()));
124*1157Smax.romanov@nginx.com         }
125*1157Smax.romanov@nginx.com 
126*1157Smax.romanov@nginx.com         Endpoint ep = new PojoEndpointClient(pojo, Arrays.asList(annotation.decoders()));
127*1157Smax.romanov@nginx.com 
128*1157Smax.romanov@nginx.com         Class<? extends ClientEndpointConfig.Configurator> configuratorClazz =
129*1157Smax.romanov@nginx.com                 annotation.configurator();
130*1157Smax.romanov@nginx.com 
131*1157Smax.romanov@nginx.com         ClientEndpointConfig.Configurator configurator = null;
132*1157Smax.romanov@nginx.com         if (!ClientEndpointConfig.Configurator.class.equals(
133*1157Smax.romanov@nginx.com                 configuratorClazz)) {
134*1157Smax.romanov@nginx.com             try {
135*1157Smax.romanov@nginx.com                 configurator = configuratorClazz.getConstructor().newInstance();
136*1157Smax.romanov@nginx.com             } catch (ReflectiveOperationException e) {
137*1157Smax.romanov@nginx.com                 throw new DeploymentException(sm.getString(
138*1157Smax.romanov@nginx.com                         "wsWebSocketContainer.defaultConfiguratorFail"), e);
139*1157Smax.romanov@nginx.com             }
140*1157Smax.romanov@nginx.com         }
141*1157Smax.romanov@nginx.com 
142*1157Smax.romanov@nginx.com         ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
143*1157Smax.romanov@nginx.com         // Avoid NPE when using RI API JAR - see BZ 56343
144*1157Smax.romanov@nginx.com         if (configurator != null) {
145*1157Smax.romanov@nginx.com             builder.configurator(configurator);
146*1157Smax.romanov@nginx.com         }
147*1157Smax.romanov@nginx.com         ClientEndpointConfig config = builder.
148*1157Smax.romanov@nginx.com                 decoders(Arrays.asList(annotation.decoders())).
149*1157Smax.romanov@nginx.com                 encoders(Arrays.asList(annotation.encoders())).
150*1157Smax.romanov@nginx.com                 preferredSubprotocols(Arrays.asList(annotation.subprotocols())).
151*1157Smax.romanov@nginx.com                 build();
152*1157Smax.romanov@nginx.com         return connectToServer(ep, config, path);
153*1157Smax.romanov@nginx.com     }
154*1157Smax.romanov@nginx.com 
155*1157Smax.romanov@nginx.com 
156*1157Smax.romanov@nginx.com     @Override
connectToServer(Class<?> annotatedEndpointClass, URI path)157*1157Smax.romanov@nginx.com     public Session connectToServer(Class<?> annotatedEndpointClass, URI path)
158*1157Smax.romanov@nginx.com             throws DeploymentException {
159*1157Smax.romanov@nginx.com 
160*1157Smax.romanov@nginx.com         Object pojo;
161*1157Smax.romanov@nginx.com         try {
162*1157Smax.romanov@nginx.com             pojo = annotatedEndpointClass.getConstructor().newInstance();
163*1157Smax.romanov@nginx.com         } catch (ReflectiveOperationException e) {
164*1157Smax.romanov@nginx.com             throw new DeploymentException(sm.getString(
165*1157Smax.romanov@nginx.com                     "wsWebSocketContainer.endpointCreateFail",
166*1157Smax.romanov@nginx.com                     annotatedEndpointClass.getName()), e);
167*1157Smax.romanov@nginx.com         }
168*1157Smax.romanov@nginx.com 
169*1157Smax.romanov@nginx.com         return connectToServer(pojo, path);
170*1157Smax.romanov@nginx.com     }
171*1157Smax.romanov@nginx.com 
172*1157Smax.romanov@nginx.com 
173*1157Smax.romanov@nginx.com     @Override
connectToServer(Class<? extends Endpoint> clazz, ClientEndpointConfig clientEndpointConfiguration, URI path)174*1157Smax.romanov@nginx.com     public Session connectToServer(Class<? extends Endpoint> clazz,
175*1157Smax.romanov@nginx.com             ClientEndpointConfig clientEndpointConfiguration, URI path)
176*1157Smax.romanov@nginx.com             throws DeploymentException {
177*1157Smax.romanov@nginx.com 
178*1157Smax.romanov@nginx.com         Endpoint endpoint;
179*1157Smax.romanov@nginx.com         try {
180*1157Smax.romanov@nginx.com             endpoint = clazz.getConstructor().newInstance();
181*1157Smax.romanov@nginx.com         } catch (ReflectiveOperationException e) {
182*1157Smax.romanov@nginx.com             throw new DeploymentException(sm.getString(
183*1157Smax.romanov@nginx.com                     "wsWebSocketContainer.endpointCreateFail", clazz.getName()),
184*1157Smax.romanov@nginx.com                     e);
185*1157Smax.romanov@nginx.com         }
186*1157Smax.romanov@nginx.com 
187*1157Smax.romanov@nginx.com         return connectToServer(endpoint, clientEndpointConfiguration, path);
188*1157Smax.romanov@nginx.com     }
189*1157Smax.romanov@nginx.com 
190*1157Smax.romanov@nginx.com 
191*1157Smax.romanov@nginx.com     @Override
connectToServer(Endpoint endpoint, ClientEndpointConfig clientEndpointConfiguration, URI path)192*1157Smax.romanov@nginx.com     public Session connectToServer(Endpoint endpoint,
193*1157Smax.romanov@nginx.com             ClientEndpointConfig clientEndpointConfiguration, URI path)
194*1157Smax.romanov@nginx.com             throws DeploymentException {
195*1157Smax.romanov@nginx.com         return connectToServerRecursive(endpoint, clientEndpointConfiguration, path, new HashSet<>());
196*1157Smax.romanov@nginx.com     }
197*1157Smax.romanov@nginx.com 
connectToServerRecursive(Endpoint endpoint, ClientEndpointConfig clientEndpointConfiguration, URI path, Set<URI> redirectSet)198*1157Smax.romanov@nginx.com     private Session connectToServerRecursive(Endpoint endpoint,
199*1157Smax.romanov@nginx.com             ClientEndpointConfig clientEndpointConfiguration, URI path,
200*1157Smax.romanov@nginx.com             Set<URI> redirectSet)
201*1157Smax.romanov@nginx.com             throws DeploymentException {
202*1157Smax.romanov@nginx.com 
203*1157Smax.romanov@nginx.com         boolean secure = false;
204*1157Smax.romanov@nginx.com         ByteBuffer proxyConnect = null;
205*1157Smax.romanov@nginx.com         URI proxyPath;
206*1157Smax.romanov@nginx.com 
207*1157Smax.romanov@nginx.com         // Validate scheme (and build proxyPath)
208*1157Smax.romanov@nginx.com         String scheme = path.getScheme();
209*1157Smax.romanov@nginx.com         if ("ws".equalsIgnoreCase(scheme)) {
210*1157Smax.romanov@nginx.com             proxyPath = URI.create("http" + path.toString().substring(2));
211*1157Smax.romanov@nginx.com         } else if ("wss".equalsIgnoreCase(scheme)) {
212*1157Smax.romanov@nginx.com             proxyPath = URI.create("https" + path.toString().substring(3));
213*1157Smax.romanov@nginx.com             secure = true;
214*1157Smax.romanov@nginx.com         } else {
215*1157Smax.romanov@nginx.com             throw new DeploymentException(sm.getString(
216*1157Smax.romanov@nginx.com                     "wsWebSocketContainer.pathWrongScheme", scheme));
217*1157Smax.romanov@nginx.com         }
218*1157Smax.romanov@nginx.com 
219*1157Smax.romanov@nginx.com         // Validate host
220*1157Smax.romanov@nginx.com         String host = path.getHost();
221*1157Smax.romanov@nginx.com         if (host == null) {
222*1157Smax.romanov@nginx.com             throw new DeploymentException(
223*1157Smax.romanov@nginx.com                     sm.getString("wsWebSocketContainer.pathNoHost"));
224*1157Smax.romanov@nginx.com         }
225*1157Smax.romanov@nginx.com         int port = path.getPort();
226*1157Smax.romanov@nginx.com 
227*1157Smax.romanov@nginx.com         SocketAddress sa = null;
228*1157Smax.romanov@nginx.com 
229*1157Smax.romanov@nginx.com         // Check to see if a proxy is configured. Javadoc indicates return value
230*1157Smax.romanov@nginx.com         // will never be null
231*1157Smax.romanov@nginx.com         List<Proxy> proxies = ProxySelector.getDefault().select(proxyPath);
232*1157Smax.romanov@nginx.com         Proxy selectedProxy = null;
233*1157Smax.romanov@nginx.com         for (Proxy proxy : proxies) {
234*1157Smax.romanov@nginx.com             if (proxy.type().equals(Proxy.Type.HTTP)) {
235*1157Smax.romanov@nginx.com                 sa = proxy.address();
236*1157Smax.romanov@nginx.com                 if (sa instanceof InetSocketAddress) {
237*1157Smax.romanov@nginx.com                     InetSocketAddress inet = (InetSocketAddress) sa;
238*1157Smax.romanov@nginx.com                     if (inet.isUnresolved()) {
239*1157Smax.romanov@nginx.com                         sa = new InetSocketAddress(inet.getHostName(), inet.getPort());
240*1157Smax.romanov@nginx.com                     }
241*1157Smax.romanov@nginx.com                 }
242*1157Smax.romanov@nginx.com                 selectedProxy = proxy;
243*1157Smax.romanov@nginx.com                 break;
244*1157Smax.romanov@nginx.com             }
245*1157Smax.romanov@nginx.com         }
246*1157Smax.romanov@nginx.com 
247*1157Smax.romanov@nginx.com         // If the port is not explicitly specified, compute it based on the
248*1157Smax.romanov@nginx.com         // scheme
249*1157Smax.romanov@nginx.com         if (port == -1) {
250*1157Smax.romanov@nginx.com             if ("ws".equalsIgnoreCase(scheme)) {
251*1157Smax.romanov@nginx.com                 port = 80;
252*1157Smax.romanov@nginx.com             } else {
253*1157Smax.romanov@nginx.com                 // Must be wss due to scheme validation above
254*1157Smax.romanov@nginx.com                 port = 443;
255*1157Smax.romanov@nginx.com             }
256*1157Smax.romanov@nginx.com         }
257*1157Smax.romanov@nginx.com 
258*1157Smax.romanov@nginx.com         // If sa is null, no proxy is configured so need to create sa
259*1157Smax.romanov@nginx.com         if (sa == null) {
260*1157Smax.romanov@nginx.com             sa = new InetSocketAddress(host, port);
261*1157Smax.romanov@nginx.com         } else {
262*1157Smax.romanov@nginx.com             proxyConnect = createProxyRequest(host, port);
263*1157Smax.romanov@nginx.com         }
264*1157Smax.romanov@nginx.com 
265*1157Smax.romanov@nginx.com         // Create the initial HTTP request to open the WebSocket connection
266*1157Smax.romanov@nginx.com         Map<String, List<String>> reqHeaders = createRequestHeaders(host, port,
267*1157Smax.romanov@nginx.com                 clientEndpointConfiguration);
268*1157Smax.romanov@nginx.com         clientEndpointConfiguration.getConfigurator().beforeRequest(reqHeaders);
269*1157Smax.romanov@nginx.com         if (Constants.DEFAULT_ORIGIN_HEADER_VALUE != null
270*1157Smax.romanov@nginx.com                 && !reqHeaders.containsKey(Constants.ORIGIN_HEADER_NAME)) {
271*1157Smax.romanov@nginx.com             List<String> originValues = new ArrayList<>(1);
272*1157Smax.romanov@nginx.com             originValues.add(Constants.DEFAULT_ORIGIN_HEADER_VALUE);
273*1157Smax.romanov@nginx.com             reqHeaders.put(Constants.ORIGIN_HEADER_NAME, originValues);
274*1157Smax.romanov@nginx.com         }
275*1157Smax.romanov@nginx.com         ByteBuffer request = createRequest(path, reqHeaders);
276*1157Smax.romanov@nginx.com 
277*1157Smax.romanov@nginx.com         AsynchronousSocketChannel socketChannel;
278*1157Smax.romanov@nginx.com         try {
279*1157Smax.romanov@nginx.com             socketChannel = AsynchronousSocketChannel.open(getAsynchronousChannelGroup());
280*1157Smax.romanov@nginx.com         } catch (IOException ioe) {
281*1157Smax.romanov@nginx.com             throw new DeploymentException(sm.getString(
282*1157Smax.romanov@nginx.com                     "wsWebSocketContainer.asynchronousSocketChannelFail"), ioe);
283*1157Smax.romanov@nginx.com         }
284*1157Smax.romanov@nginx.com 
285*1157Smax.romanov@nginx.com         Map<String,Object> userProperties = clientEndpointConfiguration.getUserProperties();
286*1157Smax.romanov@nginx.com 
287*1157Smax.romanov@nginx.com         // Get the connection timeout
288*1157Smax.romanov@nginx.com         long timeout = Constants.IO_TIMEOUT_MS_DEFAULT;
289*1157Smax.romanov@nginx.com         String timeoutValue = (String) userProperties.get(Constants.IO_TIMEOUT_MS_PROPERTY);
290*1157Smax.romanov@nginx.com         if (timeoutValue != null) {
291*1157Smax.romanov@nginx.com             timeout = Long.valueOf(timeoutValue).intValue();
292*1157Smax.romanov@nginx.com         }
293*1157Smax.romanov@nginx.com 
294*1157Smax.romanov@nginx.com         // Set-up
295*1157Smax.romanov@nginx.com         // Same size as the WsFrame input buffer
296*1157Smax.romanov@nginx.com         ByteBuffer response = ByteBuffer.allocate(getDefaultMaxBinaryMessageBufferSize());
297*1157Smax.romanov@nginx.com         String subProtocol;
298*1157Smax.romanov@nginx.com         boolean success = false;
299*1157Smax.romanov@nginx.com         List<Extension> extensionsAgreed = new ArrayList<>();
300*1157Smax.romanov@nginx.com         Transformation transformation = null;
301*1157Smax.romanov@nginx.com 
302*1157Smax.romanov@nginx.com         // Open the connection
303*1157Smax.romanov@nginx.com         Future<Void> fConnect = socketChannel.connect(sa);
304*1157Smax.romanov@nginx.com         AsyncChannelWrapper channel = null;
305*1157Smax.romanov@nginx.com 
306*1157Smax.romanov@nginx.com         if (proxyConnect != null) {
307*1157Smax.romanov@nginx.com             try {
308*1157Smax.romanov@nginx.com                 fConnect.get(timeout, TimeUnit.MILLISECONDS);
309*1157Smax.romanov@nginx.com                 // Proxy CONNECT is clear text
310*1157Smax.romanov@nginx.com                 channel = new AsyncChannelWrapperNonSecure(socketChannel);
311*1157Smax.romanov@nginx.com                 writeRequest(channel, proxyConnect, timeout);
312*1157Smax.romanov@nginx.com                 HttpResponse httpResponse = processResponse(response, channel, timeout);
313*1157Smax.romanov@nginx.com                 if (httpResponse.getStatus() != 200) {
314*1157Smax.romanov@nginx.com                     throw new DeploymentException(sm.getString(
315*1157Smax.romanov@nginx.com                             "wsWebSocketContainer.proxyConnectFail", selectedProxy,
316*1157Smax.romanov@nginx.com                             Integer.toString(httpResponse.getStatus())));
317*1157Smax.romanov@nginx.com                 }
318*1157Smax.romanov@nginx.com             } catch (TimeoutException | InterruptedException | ExecutionException |
319*1157Smax.romanov@nginx.com                     EOFException e) {
320*1157Smax.romanov@nginx.com                 if (channel != null) {
321*1157Smax.romanov@nginx.com                     channel.close();
322*1157Smax.romanov@nginx.com                 }
323*1157Smax.romanov@nginx.com                 throw new DeploymentException(
324*1157Smax.romanov@nginx.com                         sm.getString("wsWebSocketContainer.httpRequestFailed"), e);
325*1157Smax.romanov@nginx.com             }
326*1157Smax.romanov@nginx.com         }
327*1157Smax.romanov@nginx.com 
328*1157Smax.romanov@nginx.com         if (secure) {
329*1157Smax.romanov@nginx.com             // Regardless of whether a non-secure wrapper was created for a
330*1157Smax.romanov@nginx.com             // proxy CONNECT, need to use TLS from this point on so wrap the
331*1157Smax.romanov@nginx.com             // original AsynchronousSocketChannel
332*1157Smax.romanov@nginx.com             SSLEngine sslEngine = createSSLEngine(userProperties, host, port);
333*1157Smax.romanov@nginx.com             channel = new AsyncChannelWrapperSecure(socketChannel, sslEngine);
334*1157Smax.romanov@nginx.com         } else if (channel == null) {
335*1157Smax.romanov@nginx.com             // Only need to wrap as this point if it wasn't wrapped to process a
336*1157Smax.romanov@nginx.com             // proxy CONNECT
337*1157Smax.romanov@nginx.com             channel = new AsyncChannelWrapperNonSecure(socketChannel);
338*1157Smax.romanov@nginx.com         }
339*1157Smax.romanov@nginx.com 
340*1157Smax.romanov@nginx.com         try {
341*1157Smax.romanov@nginx.com             fConnect.get(timeout, TimeUnit.MILLISECONDS);
342*1157Smax.romanov@nginx.com 
343*1157Smax.romanov@nginx.com             Future<Void> fHandshake = channel.handshake();
344*1157Smax.romanov@nginx.com             fHandshake.get(timeout, TimeUnit.MILLISECONDS);
345*1157Smax.romanov@nginx.com 
346*1157Smax.romanov@nginx.com             writeRequest(channel, request, timeout);
347*1157Smax.romanov@nginx.com 
348*1157Smax.romanov@nginx.com             HttpResponse httpResponse = processResponse(response, channel, timeout);
349*1157Smax.romanov@nginx.com 
350*1157Smax.romanov@nginx.com             // Check maximum permitted redirects
351*1157Smax.romanov@nginx.com             int maxRedirects = Constants.MAX_REDIRECTIONS_DEFAULT;
352*1157Smax.romanov@nginx.com             String maxRedirectsValue =
353*1157Smax.romanov@nginx.com                     (String) userProperties.get(Constants.MAX_REDIRECTIONS_PROPERTY);
354*1157Smax.romanov@nginx.com             if (maxRedirectsValue != null) {
355*1157Smax.romanov@nginx.com                 maxRedirects = Integer.parseInt(maxRedirectsValue);
356*1157Smax.romanov@nginx.com             }
357*1157Smax.romanov@nginx.com 
358*1157Smax.romanov@nginx.com             if (httpResponse.status != 101) {
359*1157Smax.romanov@nginx.com                 if(isRedirectStatus(httpResponse.status)){
360*1157Smax.romanov@nginx.com                     List<String> locationHeader =
361*1157Smax.romanov@nginx.com                             httpResponse.getHandshakeResponse().getHeaders().get(
362*1157Smax.romanov@nginx.com                                     Constants.LOCATION_HEADER_NAME);
363*1157Smax.romanov@nginx.com 
364*1157Smax.romanov@nginx.com                     if (locationHeader == null || locationHeader.isEmpty() ||
365*1157Smax.romanov@nginx.com                             locationHeader.get(0) == null || locationHeader.get(0).isEmpty()) {
366*1157Smax.romanov@nginx.com                         throw new DeploymentException(sm.getString(
367*1157Smax.romanov@nginx.com                                 "wsWebSocketContainer.missingLocationHeader",
368*1157Smax.romanov@nginx.com                                 Integer.toString(httpResponse.status)));
369*1157Smax.romanov@nginx.com                     }
370*1157Smax.romanov@nginx.com 
371*1157Smax.romanov@nginx.com                     URI redirectLocation = URI.create(locationHeader.get(0)).normalize();
372*1157Smax.romanov@nginx.com 
373*1157Smax.romanov@nginx.com                     if (!redirectLocation.isAbsolute()) {
374*1157Smax.romanov@nginx.com                         redirectLocation = path.resolve(redirectLocation);
375*1157Smax.romanov@nginx.com                     }
376*1157Smax.romanov@nginx.com 
377*1157Smax.romanov@nginx.com                     String redirectScheme = redirectLocation.getScheme().toLowerCase();
378*1157Smax.romanov@nginx.com 
379*1157Smax.romanov@nginx.com                     if (redirectScheme.startsWith("http")) {
380*1157Smax.romanov@nginx.com                         redirectLocation = new URI(redirectScheme.replace("http", "ws"),
381*1157Smax.romanov@nginx.com                                 redirectLocation.getUserInfo(), redirectLocation.getHost(),
382*1157Smax.romanov@nginx.com                                 redirectLocation.getPort(), redirectLocation.getPath(),
383*1157Smax.romanov@nginx.com                                 redirectLocation.getQuery(), redirectLocation.getFragment());
384*1157Smax.romanov@nginx.com                     }
385*1157Smax.romanov@nginx.com 
386*1157Smax.romanov@nginx.com                     if (!redirectSet.add(redirectLocation) || redirectSet.size() > maxRedirects) {
387*1157Smax.romanov@nginx.com                         throw new DeploymentException(sm.getString(
388*1157Smax.romanov@nginx.com                                 "wsWebSocketContainer.redirectThreshold", redirectLocation,
389*1157Smax.romanov@nginx.com                                 Integer.toString(redirectSet.size()),
390*1157Smax.romanov@nginx.com                                 Integer.toString(maxRedirects)));
391*1157Smax.romanov@nginx.com                     }
392*1157Smax.romanov@nginx.com 
393*1157Smax.romanov@nginx.com                     return connectToServerRecursive(endpoint, clientEndpointConfiguration, redirectLocation, redirectSet);
394*1157Smax.romanov@nginx.com 
395*1157Smax.romanov@nginx.com                 }
396*1157Smax.romanov@nginx.com 
397*1157Smax.romanov@nginx.com                 else if (httpResponse.status == 401) {
398*1157Smax.romanov@nginx.com 
399*1157Smax.romanov@nginx.com                     if (userProperties.get(Constants.AUTHORIZATION_HEADER_NAME) != null) {
400*1157Smax.romanov@nginx.com                         throw new DeploymentException(sm.getString(
401*1157Smax.romanov@nginx.com                                 "wsWebSocketContainer.failedAuthentication",
402*1157Smax.romanov@nginx.com                                 Integer.valueOf(httpResponse.status)));
403*1157Smax.romanov@nginx.com                     }
404*1157Smax.romanov@nginx.com 
405*1157Smax.romanov@nginx.com                     List<String> wwwAuthenticateHeaders = httpResponse.getHandshakeResponse()
406*1157Smax.romanov@nginx.com                             .getHeaders().get(Constants.WWW_AUTHENTICATE_HEADER_NAME);
407*1157Smax.romanov@nginx.com 
408*1157Smax.romanov@nginx.com                     if (wwwAuthenticateHeaders == null || wwwAuthenticateHeaders.isEmpty() ||
409*1157Smax.romanov@nginx.com                             wwwAuthenticateHeaders.get(0) == null || wwwAuthenticateHeaders.get(0).isEmpty()) {
410*1157Smax.romanov@nginx.com                         throw new DeploymentException(sm.getString(
411*1157Smax.romanov@nginx.com                                 "wsWebSocketContainer.missingWWWAuthenticateHeader",
412*1157Smax.romanov@nginx.com                                 Integer.toString(httpResponse.status)));
413*1157Smax.romanov@nginx.com                     }
414*1157Smax.romanov@nginx.com 
415*1157Smax.romanov@nginx.com                     String authScheme = wwwAuthenticateHeaders.get(0).split("\\s+", 2)[0];
416*1157Smax.romanov@nginx.com                     String requestUri = new String(request.array(), StandardCharsets.ISO_8859_1)
417*1157Smax.romanov@nginx.com                             .split("\\s", 3)[1];
418*1157Smax.romanov@nginx.com 
419*1157Smax.romanov@nginx.com                     Authenticator auth = AuthenticatorFactory.getAuthenticator(authScheme);
420*1157Smax.romanov@nginx.com 
421*1157Smax.romanov@nginx.com                     if (auth == null) {
422*1157Smax.romanov@nginx.com                         throw new DeploymentException(
423*1157Smax.romanov@nginx.com                                 sm.getString("wsWebSocketContainer.unsupportedAuthScheme",
424*1157Smax.romanov@nginx.com                                         Integer.valueOf(httpResponse.status), authScheme));
425*1157Smax.romanov@nginx.com                     }
426*1157Smax.romanov@nginx.com 
427*1157Smax.romanov@nginx.com                     userProperties.put(Constants.AUTHORIZATION_HEADER_NAME, auth.getAuthorization(
428*1157Smax.romanov@nginx.com                             requestUri, wwwAuthenticateHeaders.get(0), userProperties));
429*1157Smax.romanov@nginx.com 
430*1157Smax.romanov@nginx.com                     return connectToServerRecursive(endpoint, clientEndpointConfiguration, path, redirectSet);
431*1157Smax.romanov@nginx.com 
432*1157Smax.romanov@nginx.com                 }
433*1157Smax.romanov@nginx.com 
434*1157Smax.romanov@nginx.com                 else {
435*1157Smax.romanov@nginx.com                     throw new DeploymentException(sm.getString("wsWebSocketContainer.invalidStatus",
436*1157Smax.romanov@nginx.com                             Integer.toString(httpResponse.status)));
437*1157Smax.romanov@nginx.com                 }
438*1157Smax.romanov@nginx.com             }
439*1157Smax.romanov@nginx.com             HandshakeResponse handshakeResponse = httpResponse.getHandshakeResponse();
440*1157Smax.romanov@nginx.com             clientEndpointConfiguration.getConfigurator().afterResponse(handshakeResponse);
441*1157Smax.romanov@nginx.com 
442*1157Smax.romanov@nginx.com             // Sub-protocol
443*1157Smax.romanov@nginx.com             List<String> protocolHeaders = handshakeResponse.getHeaders().get(
444*1157Smax.romanov@nginx.com                     Constants.WS_PROTOCOL_HEADER_NAME);
445*1157Smax.romanov@nginx.com             if (protocolHeaders == null || protocolHeaders.size() == 0) {
446*1157Smax.romanov@nginx.com                 subProtocol = null;
447*1157Smax.romanov@nginx.com             } else if (protocolHeaders.size() == 1) {
448*1157Smax.romanov@nginx.com                 subProtocol = protocolHeaders.get(0);
449*1157Smax.romanov@nginx.com             } else {
450*1157Smax.romanov@nginx.com                 throw new DeploymentException(
451*1157Smax.romanov@nginx.com                         sm.getString("wsWebSocketContainer.invalidSubProtocol"));
452*1157Smax.romanov@nginx.com             }
453*1157Smax.romanov@nginx.com 
454*1157Smax.romanov@nginx.com             // Extensions
455*1157Smax.romanov@nginx.com             // Should normally only be one header but handle the case of
456*1157Smax.romanov@nginx.com             // multiple headers
457*1157Smax.romanov@nginx.com             List<String> extHeaders = handshakeResponse.getHeaders().get(
458*1157Smax.romanov@nginx.com                     Constants.WS_EXTENSIONS_HEADER_NAME);
459*1157Smax.romanov@nginx.com             if (extHeaders != null) {
460*1157Smax.romanov@nginx.com                 for (String extHeader : extHeaders) {
461*1157Smax.romanov@nginx.com                     Util.parseExtensionHeader(extensionsAgreed, extHeader);
462*1157Smax.romanov@nginx.com                 }
463*1157Smax.romanov@nginx.com             }
464*1157Smax.romanov@nginx.com 
465*1157Smax.romanov@nginx.com             // Build the transformations
466*1157Smax.romanov@nginx.com             TransformationFactory factory = TransformationFactory.getInstance();
467*1157Smax.romanov@nginx.com             for (Extension extension : extensionsAgreed) {
468*1157Smax.romanov@nginx.com                 List<List<Extension.Parameter>> wrapper = new ArrayList<>(1);
469*1157Smax.romanov@nginx.com                 wrapper.add(extension.getParameters());
470*1157Smax.romanov@nginx.com                 Transformation t = factory.create(extension.getName(), wrapper, false);
471*1157Smax.romanov@nginx.com                 if (t == null) {
472*1157Smax.romanov@nginx.com                     throw new DeploymentException(sm.getString(
473*1157Smax.romanov@nginx.com                             "wsWebSocketContainer.invalidExtensionParameters"));
474*1157Smax.romanov@nginx.com                 }
475*1157Smax.romanov@nginx.com                 if (transformation == null) {
476*1157Smax.romanov@nginx.com                     transformation = t;
477*1157Smax.romanov@nginx.com                 } else {
478*1157Smax.romanov@nginx.com                     transformation.setNext(t);
479*1157Smax.romanov@nginx.com                 }
480*1157Smax.romanov@nginx.com             }
481*1157Smax.romanov@nginx.com 
482*1157Smax.romanov@nginx.com             success = true;
483*1157Smax.romanov@nginx.com         } catch (ExecutionException | InterruptedException | SSLException |
484*1157Smax.romanov@nginx.com                 EOFException | TimeoutException | URISyntaxException | AuthenticationException e) {
485*1157Smax.romanov@nginx.com             throw new DeploymentException(
486*1157Smax.romanov@nginx.com                     sm.getString("wsWebSocketContainer.httpRequestFailed"), e);
487*1157Smax.romanov@nginx.com         } finally {
488*1157Smax.romanov@nginx.com             if (!success) {
489*1157Smax.romanov@nginx.com                 channel.close();
490*1157Smax.romanov@nginx.com             }
491*1157Smax.romanov@nginx.com         }
492*1157Smax.romanov@nginx.com 
493*1157Smax.romanov@nginx.com         // Switch to WebSocket
494*1157Smax.romanov@nginx.com         WsRemoteEndpointImplClient wsRemoteEndpointClient = new WsRemoteEndpointImplClient(channel);
495*1157Smax.romanov@nginx.com 
496*1157Smax.romanov@nginx.com         WsSession wsSession = new WsSession(endpoint, wsRemoteEndpointClient,
497*1157Smax.romanov@nginx.com                 this, null, null, null, null, null, extensionsAgreed,
498*1157Smax.romanov@nginx.com                 subProtocol, Collections.<String,String>emptyMap(), secure,
499*1157Smax.romanov@nginx.com                 clientEndpointConfiguration, null);
500*1157Smax.romanov@nginx.com 
501*1157Smax.romanov@nginx.com         WsFrameClient wsFrameClient = new WsFrameClient(response, channel,
502*1157Smax.romanov@nginx.com                 wsSession, transformation);
503*1157Smax.romanov@nginx.com         // WsFrame adds the necessary final transformations. Copy the
504*1157Smax.romanov@nginx.com         // completed transformation chain to the remote end point.
505*1157Smax.romanov@nginx.com         wsRemoteEndpointClient.setTransformation(wsFrameClient.getTransformation());
506*1157Smax.romanov@nginx.com 
507*1157Smax.romanov@nginx.com         endpoint.onOpen(wsSession, clientEndpointConfiguration);
508*1157Smax.romanov@nginx.com         registerSession(endpoint, wsSession);
509*1157Smax.romanov@nginx.com 
510*1157Smax.romanov@nginx.com         /* It is possible that the server sent one or more messages as soon as
511*1157Smax.romanov@nginx.com          * the WebSocket connection was established. Depending on the exact
512*1157Smax.romanov@nginx.com          * timing of when those messages were sent they could be sat in the
513*1157Smax.romanov@nginx.com          * input buffer waiting to be read and will not trigger a "data
514*1157Smax.romanov@nginx.com          * available to read" event. Therefore, it is necessary to process the
515*1157Smax.romanov@nginx.com          * input buffer here. Note that this happens on the current thread which
516*1157Smax.romanov@nginx.com          * means that this thread will be used for any onMessage notifications.
517*1157Smax.romanov@nginx.com          * This is a special case. Subsequent "data available to read" events
518*1157Smax.romanov@nginx.com          * will be handled by threads from the AsyncChannelGroup's executor.
519*1157Smax.romanov@nginx.com          */
520*1157Smax.romanov@nginx.com         wsFrameClient.startInputProcessing();
521*1157Smax.romanov@nginx.com 
522*1157Smax.romanov@nginx.com         return wsSession;
523*1157Smax.romanov@nginx.com     }
524*1157Smax.romanov@nginx.com 
525*1157Smax.romanov@nginx.com 
writeRequest(AsyncChannelWrapper channel, ByteBuffer request, long timeout)526*1157Smax.romanov@nginx.com     private static void writeRequest(AsyncChannelWrapper channel, ByteBuffer request,
527*1157Smax.romanov@nginx.com             long timeout) throws TimeoutException, InterruptedException, ExecutionException {
528*1157Smax.romanov@nginx.com         int toWrite = request.limit();
529*1157Smax.romanov@nginx.com 
530*1157Smax.romanov@nginx.com         Future<Integer> fWrite = channel.write(request);
531*1157Smax.romanov@nginx.com         Integer thisWrite = fWrite.get(timeout, TimeUnit.MILLISECONDS);
532*1157Smax.romanov@nginx.com         toWrite -= thisWrite.intValue();
533*1157Smax.romanov@nginx.com 
534*1157Smax.romanov@nginx.com         while (toWrite > 0) {
535*1157Smax.romanov@nginx.com             fWrite = channel.write(request);
536*1157Smax.romanov@nginx.com             thisWrite = fWrite.get(timeout, TimeUnit.MILLISECONDS);
537*1157Smax.romanov@nginx.com             toWrite -= thisWrite.intValue();
538*1157Smax.romanov@nginx.com         }
539*1157Smax.romanov@nginx.com     }
540*1157Smax.romanov@nginx.com 
541*1157Smax.romanov@nginx.com 
isRedirectStatus(int httpResponseCode)542*1157Smax.romanov@nginx.com     private static boolean isRedirectStatus(int httpResponseCode) {
543*1157Smax.romanov@nginx.com 
544*1157Smax.romanov@nginx.com         boolean isRedirect = false;
545*1157Smax.romanov@nginx.com 
546*1157Smax.romanov@nginx.com         switch (httpResponseCode) {
547*1157Smax.romanov@nginx.com         case Constants.MULTIPLE_CHOICES:
548*1157Smax.romanov@nginx.com         case Constants.MOVED_PERMANENTLY:
549*1157Smax.romanov@nginx.com         case Constants.FOUND:
550*1157Smax.romanov@nginx.com         case Constants.SEE_OTHER:
551*1157Smax.romanov@nginx.com         case Constants.USE_PROXY:
552*1157Smax.romanov@nginx.com         case Constants.TEMPORARY_REDIRECT:
553*1157Smax.romanov@nginx.com             isRedirect = true;
554*1157Smax.romanov@nginx.com             break;
555*1157Smax.romanov@nginx.com         default:
556*1157Smax.romanov@nginx.com             break;
557*1157Smax.romanov@nginx.com         }
558*1157Smax.romanov@nginx.com 
559*1157Smax.romanov@nginx.com         return isRedirect;
560*1157Smax.romanov@nginx.com     }
561*1157Smax.romanov@nginx.com 
562*1157Smax.romanov@nginx.com 
createProxyRequest(String host, int port)563*1157Smax.romanov@nginx.com     private static ByteBuffer createProxyRequest(String host, int port) {
564*1157Smax.romanov@nginx.com         StringBuilder request = new StringBuilder();
565*1157Smax.romanov@nginx.com         request.append("CONNECT ");
566*1157Smax.romanov@nginx.com         request.append(host);
567*1157Smax.romanov@nginx.com         request.append(':');
568*1157Smax.romanov@nginx.com         request.append(port);
569*1157Smax.romanov@nginx.com 
570*1157Smax.romanov@nginx.com         request.append(" HTTP/1.1\r\nProxy-Connection: keep-alive\r\nConnection: keepalive\r\nHost: ");
571*1157Smax.romanov@nginx.com         request.append(host);
572*1157Smax.romanov@nginx.com         request.append(':');
573*1157Smax.romanov@nginx.com         request.append(port);
574*1157Smax.romanov@nginx.com 
575*1157Smax.romanov@nginx.com         request.append("\r\n\r\n");
576*1157Smax.romanov@nginx.com 
577*1157Smax.romanov@nginx.com         byte[] bytes = request.toString().getBytes(StandardCharsets.ISO_8859_1);
578*1157Smax.romanov@nginx.com         return ByteBuffer.wrap(bytes);
579*1157Smax.romanov@nginx.com     }
580*1157Smax.romanov@nginx.com 
registerSession(Endpoint endpoint, WsSession wsSession)581*1157Smax.romanov@nginx.com     protected void registerSession(Endpoint endpoint, WsSession wsSession) {
582*1157Smax.romanov@nginx.com 
583*1157Smax.romanov@nginx.com         if (!wsSession.isOpen()) {
584*1157Smax.romanov@nginx.com             // The session was closed during onOpen. No need to register it.
585*1157Smax.romanov@nginx.com             return;
586*1157Smax.romanov@nginx.com         }
587*1157Smax.romanov@nginx.com         synchronized (endPointSessionMapLock) {
588*1157Smax.romanov@nginx.com             if (endpointSessionMap.size() == 0) {
589*1157Smax.romanov@nginx.com                 BackgroundProcessManager.getInstance().register(this);
590*1157Smax.romanov@nginx.com             }
591*1157Smax.romanov@nginx.com             Set<WsSession> wsSessions = endpointSessionMap.get(endpoint);
592*1157Smax.romanov@nginx.com             if (wsSessions == null) {
593*1157Smax.romanov@nginx.com                 wsSessions = new HashSet<>();
594*1157Smax.romanov@nginx.com                 endpointSessionMap.put(endpoint, wsSessions);
595*1157Smax.romanov@nginx.com             }
596*1157Smax.romanov@nginx.com             wsSessions.add(wsSession);
597*1157Smax.romanov@nginx.com         }
598*1157Smax.romanov@nginx.com         sessions.put(wsSession, wsSession);
599*1157Smax.romanov@nginx.com     }
600*1157Smax.romanov@nginx.com 
601*1157Smax.romanov@nginx.com 
unregisterSession(Endpoint endpoint, WsSession wsSession)602*1157Smax.romanov@nginx.com     protected void unregisterSession(Endpoint endpoint, WsSession wsSession) {
603*1157Smax.romanov@nginx.com 
604*1157Smax.romanov@nginx.com         synchronized (endPointSessionMapLock) {
605*1157Smax.romanov@nginx.com             Set<WsSession> wsSessions = endpointSessionMap.get(endpoint);
606*1157Smax.romanov@nginx.com             if (wsSessions != null) {
607*1157Smax.romanov@nginx.com                 wsSessions.remove(wsSession);
608*1157Smax.romanov@nginx.com                 if (wsSessions.size() == 0) {
609*1157Smax.romanov@nginx.com                     endpointSessionMap.remove(endpoint);
610*1157Smax.romanov@nginx.com                 }
611*1157Smax.romanov@nginx.com             }
612*1157Smax.romanov@nginx.com             if (endpointSessionMap.size() == 0) {
613*1157Smax.romanov@nginx.com                 BackgroundProcessManager.getInstance().unregister(this);
614*1157Smax.romanov@nginx.com             }
615*1157Smax.romanov@nginx.com         }
616*1157Smax.romanov@nginx.com         sessions.remove(wsSession);
617*1157Smax.romanov@nginx.com     }
618*1157Smax.romanov@nginx.com 
619*1157Smax.romanov@nginx.com 
getOpenSessions(Endpoint endpoint)620*1157Smax.romanov@nginx.com     Set<Session> getOpenSessions(Endpoint endpoint) {
621*1157Smax.romanov@nginx.com         HashSet<Session> result = new HashSet<>();
622*1157Smax.romanov@nginx.com         synchronized (endPointSessionMapLock) {
623*1157Smax.romanov@nginx.com             Set<WsSession> sessions = endpointSessionMap.get(endpoint);
624*1157Smax.romanov@nginx.com             if (sessions != null) {
625*1157Smax.romanov@nginx.com                 result.addAll(sessions);
626*1157Smax.romanov@nginx.com             }
627*1157Smax.romanov@nginx.com         }
628*1157Smax.romanov@nginx.com         return result;
629*1157Smax.romanov@nginx.com     }
630*1157Smax.romanov@nginx.com 
createRequestHeaders(String host, int port, ClientEndpointConfig clientEndpointConfiguration)631*1157Smax.romanov@nginx.com     private static Map<String, List<String>> createRequestHeaders(String host, int port,
632*1157Smax.romanov@nginx.com             ClientEndpointConfig clientEndpointConfiguration) {
633*1157Smax.romanov@nginx.com 
634*1157Smax.romanov@nginx.com         Map<String, List<String>> headers = new HashMap<>();
635*1157Smax.romanov@nginx.com         List<Extension> extensions = clientEndpointConfiguration.getExtensions();
636*1157Smax.romanov@nginx.com         List<String> subProtocols = clientEndpointConfiguration.getPreferredSubprotocols();
637*1157Smax.romanov@nginx.com         Map<String, Object> userProperties = clientEndpointConfiguration.getUserProperties();
638*1157Smax.romanov@nginx.com 
639*1157Smax.romanov@nginx.com         if (userProperties.get(Constants.AUTHORIZATION_HEADER_NAME) != null) {
640*1157Smax.romanov@nginx.com             List<String> authValues = new ArrayList<>(1);
641*1157Smax.romanov@nginx.com             authValues.add((String) userProperties.get(Constants.AUTHORIZATION_HEADER_NAME));
642*1157Smax.romanov@nginx.com             headers.put(Constants.AUTHORIZATION_HEADER_NAME, authValues);
643*1157Smax.romanov@nginx.com         }
644*1157Smax.romanov@nginx.com 
645*1157Smax.romanov@nginx.com         // Host header
646*1157Smax.romanov@nginx.com         List<String> hostValues = new ArrayList<>(1);
647*1157Smax.romanov@nginx.com         if (port == -1) {
648*1157Smax.romanov@nginx.com             hostValues.add(host);
649*1157Smax.romanov@nginx.com         } else {
650*1157Smax.romanov@nginx.com             hostValues.add(host + ':' + port);
651*1157Smax.romanov@nginx.com         }
652*1157Smax.romanov@nginx.com 
653*1157Smax.romanov@nginx.com         headers.put(Constants.HOST_HEADER_NAME, hostValues);
654*1157Smax.romanov@nginx.com 
655*1157Smax.romanov@nginx.com         // Upgrade header
656*1157Smax.romanov@nginx.com         List<String> upgradeValues = new ArrayList<>(1);
657*1157Smax.romanov@nginx.com         upgradeValues.add(Constants.UPGRADE_HEADER_VALUE);
658*1157Smax.romanov@nginx.com         headers.put(Constants.UPGRADE_HEADER_NAME, upgradeValues);
659*1157Smax.romanov@nginx.com 
660*1157Smax.romanov@nginx.com         // Connection header
661*1157Smax.romanov@nginx.com         List<String> connectionValues = new ArrayList<>(1);
662*1157Smax.romanov@nginx.com         connectionValues.add(Constants.CONNECTION_HEADER_VALUE);
663*1157Smax.romanov@nginx.com         headers.put(Constants.CONNECTION_HEADER_NAME, connectionValues);
664*1157Smax.romanov@nginx.com 
665*1157Smax.romanov@nginx.com         // WebSocket version header
666*1157Smax.romanov@nginx.com         List<String> wsVersionValues = new ArrayList<>(1);
667*1157Smax.romanov@nginx.com         wsVersionValues.add(Constants.WS_VERSION_HEADER_VALUE);
668*1157Smax.romanov@nginx.com         headers.put(Constants.WS_VERSION_HEADER_NAME, wsVersionValues);
669*1157Smax.romanov@nginx.com 
670*1157Smax.romanov@nginx.com         // WebSocket key
671*1157Smax.romanov@nginx.com         List<String> wsKeyValues = new ArrayList<>(1);
672*1157Smax.romanov@nginx.com         wsKeyValues.add(generateWsKeyValue());
673*1157Smax.romanov@nginx.com         headers.put(Constants.WS_KEY_HEADER_NAME, wsKeyValues);
674*1157Smax.romanov@nginx.com 
675*1157Smax.romanov@nginx.com         // WebSocket sub-protocols
676*1157Smax.romanov@nginx.com         if (subProtocols != null && subProtocols.size() > 0) {
677*1157Smax.romanov@nginx.com             headers.put(Constants.WS_PROTOCOL_HEADER_NAME, subProtocols);
678*1157Smax.romanov@nginx.com         }
679*1157Smax.romanov@nginx.com 
680*1157Smax.romanov@nginx.com         // WebSocket extensions
681*1157Smax.romanov@nginx.com         if (extensions != null && extensions.size() > 0) {
682*1157Smax.romanov@nginx.com             headers.put(Constants.WS_EXTENSIONS_HEADER_NAME,
683*1157Smax.romanov@nginx.com                     generateExtensionHeaders(extensions));
684*1157Smax.romanov@nginx.com         }
685*1157Smax.romanov@nginx.com 
686*1157Smax.romanov@nginx.com         return headers;
687*1157Smax.romanov@nginx.com     }
688*1157Smax.romanov@nginx.com 
689*1157Smax.romanov@nginx.com 
generateExtensionHeaders(List<Extension> extensions)690*1157Smax.romanov@nginx.com     private static List<String> generateExtensionHeaders(List<Extension> extensions) {
691*1157Smax.romanov@nginx.com         List<String> result = new ArrayList<>(extensions.size());
692*1157Smax.romanov@nginx.com         for (Extension extension : extensions) {
693*1157Smax.romanov@nginx.com             StringBuilder header = new StringBuilder();
694*1157Smax.romanov@nginx.com             header.append(extension.getName());
695*1157Smax.romanov@nginx.com             for (Extension.Parameter param : extension.getParameters()) {
696*1157Smax.romanov@nginx.com                 header.append(';');
697*1157Smax.romanov@nginx.com                 header.append(param.getName());
698*1157Smax.romanov@nginx.com                 String value = param.getValue();
699*1157Smax.romanov@nginx.com                 if (value != null && value.length() > 0) {
700*1157Smax.romanov@nginx.com                     header.append('=');
701*1157Smax.romanov@nginx.com                     header.append(value);
702*1157Smax.romanov@nginx.com                 }
703*1157Smax.romanov@nginx.com             }
704*1157Smax.romanov@nginx.com             result.add(header.toString());
705*1157Smax.romanov@nginx.com         }
706*1157Smax.romanov@nginx.com         return result;
707*1157Smax.romanov@nginx.com     }
708*1157Smax.romanov@nginx.com 
709*1157Smax.romanov@nginx.com 
generateWsKeyValue()710*1157Smax.romanov@nginx.com     private static String generateWsKeyValue() {
711*1157Smax.romanov@nginx.com         byte[] keyBytes = new byte[16];
712*1157Smax.romanov@nginx.com         RANDOM.nextBytes(keyBytes);
713*1157Smax.romanov@nginx.com         return Base64.encodeBase64String(keyBytes);
714*1157Smax.romanov@nginx.com     }
715*1157Smax.romanov@nginx.com 
716*1157Smax.romanov@nginx.com 
createRequest(URI uri, Map<String,List<String>> reqHeaders)717*1157Smax.romanov@nginx.com     private static ByteBuffer createRequest(URI uri, Map<String,List<String>> reqHeaders) {
718*1157Smax.romanov@nginx.com         ByteBuffer result = ByteBuffer.allocate(4 * 1024);
719*1157Smax.romanov@nginx.com 
720*1157Smax.romanov@nginx.com         // Request line
721*1157Smax.romanov@nginx.com         result.put(GET_BYTES);
722*1157Smax.romanov@nginx.com         if (null == uri.getPath() || "".equals(uri.getPath())) {
723*1157Smax.romanov@nginx.com             result.put(ROOT_URI_BYTES);
724*1157Smax.romanov@nginx.com         } else {
725*1157Smax.romanov@nginx.com             result.put(uri.getRawPath().getBytes(StandardCharsets.ISO_8859_1));
726*1157Smax.romanov@nginx.com         }
727*1157Smax.romanov@nginx.com         String query = uri.getRawQuery();
728*1157Smax.romanov@nginx.com         if (query != null) {
729*1157Smax.romanov@nginx.com             result.put((byte) '?');
730*1157Smax.romanov@nginx.com             result.put(query.getBytes(StandardCharsets.ISO_8859_1));
731*1157Smax.romanov@nginx.com         }
732*1157Smax.romanov@nginx.com         result.put(HTTP_VERSION_BYTES);
733*1157Smax.romanov@nginx.com 
734*1157Smax.romanov@nginx.com         // Headers
735*1157Smax.romanov@nginx.com         for (Entry<String, List<String>> entry : reqHeaders.entrySet()) {
736*1157Smax.romanov@nginx.com             result = addHeader(result, entry.getKey(), entry.getValue());
737*1157Smax.romanov@nginx.com         }
738*1157Smax.romanov@nginx.com 
739*1157Smax.romanov@nginx.com         // Terminating CRLF
740*1157Smax.romanov@nginx.com         result.put(CRLF);
741*1157Smax.romanov@nginx.com 
742*1157Smax.romanov@nginx.com         result.flip();
743*1157Smax.romanov@nginx.com 
744*1157Smax.romanov@nginx.com         return result;
745*1157Smax.romanov@nginx.com     }
746*1157Smax.romanov@nginx.com 
747*1157Smax.romanov@nginx.com 
addHeader(ByteBuffer result, String key, List<String> values)748*1157Smax.romanov@nginx.com     private static ByteBuffer addHeader(ByteBuffer result, String key, List<String> values) {
749*1157Smax.romanov@nginx.com         if (values.isEmpty()) {
750*1157Smax.romanov@nginx.com             return result;
751*1157Smax.romanov@nginx.com         }
752*1157Smax.romanov@nginx.com 
753*1157Smax.romanov@nginx.com         result = putWithExpand(result, key.getBytes(StandardCharsets.ISO_8859_1));
754*1157Smax.romanov@nginx.com         result = putWithExpand(result, ": ".getBytes(StandardCharsets.ISO_8859_1));
755*1157Smax.romanov@nginx.com         result = putWithExpand(result, StringUtils.join(values).getBytes(StandardCharsets.ISO_8859_1));
756*1157Smax.romanov@nginx.com         result = putWithExpand(result, CRLF);
757*1157Smax.romanov@nginx.com 
758*1157Smax.romanov@nginx.com         return result;
759*1157Smax.romanov@nginx.com     }
760*1157Smax.romanov@nginx.com 
761*1157Smax.romanov@nginx.com 
putWithExpand(ByteBuffer input, byte[] bytes)762*1157Smax.romanov@nginx.com     private static ByteBuffer putWithExpand(ByteBuffer input, byte[] bytes) {
763*1157Smax.romanov@nginx.com         if (bytes.length > input.remaining()) {
764*1157Smax.romanov@nginx.com             int newSize;
765*1157Smax.romanov@nginx.com             if (bytes.length > input.capacity()) {
766*1157Smax.romanov@nginx.com                 newSize = 2 * bytes.length;
767*1157Smax.romanov@nginx.com             } else {
768*1157Smax.romanov@nginx.com                 newSize = input.capacity() * 2;
769*1157Smax.romanov@nginx.com             }
770*1157Smax.romanov@nginx.com             ByteBuffer expanded = ByteBuffer.allocate(newSize);
771*1157Smax.romanov@nginx.com             input.flip();
772*1157Smax.romanov@nginx.com             expanded.put(input);
773*1157Smax.romanov@nginx.com             input = expanded;
774*1157Smax.romanov@nginx.com         }
775*1157Smax.romanov@nginx.com         return input.put(bytes);
776*1157Smax.romanov@nginx.com     }
777*1157Smax.romanov@nginx.com 
778*1157Smax.romanov@nginx.com 
779*1157Smax.romanov@nginx.com     /**
780*1157Smax.romanov@nginx.com      * Process response, blocking until HTTP response has been fully received.
781*1157Smax.romanov@nginx.com      * @throws ExecutionException
782*1157Smax.romanov@nginx.com      * @throws InterruptedException
783*1157Smax.romanov@nginx.com      * @throws DeploymentException
784*1157Smax.romanov@nginx.com      * @throws TimeoutException
785*1157Smax.romanov@nginx.com      */
processResponse(ByteBuffer response, AsyncChannelWrapper channel, long timeout)786*1157Smax.romanov@nginx.com     private HttpResponse processResponse(ByteBuffer response,
787*1157Smax.romanov@nginx.com             AsyncChannelWrapper channel, long timeout) throws InterruptedException,
788*1157Smax.romanov@nginx.com             ExecutionException, DeploymentException, EOFException,
789*1157Smax.romanov@nginx.com             TimeoutException {
790*1157Smax.romanov@nginx.com 
791*1157Smax.romanov@nginx.com         Map<String,List<String>> headers = new CaseInsensitiveKeyMap<>();
792*1157Smax.romanov@nginx.com 
793*1157Smax.romanov@nginx.com         int status = 0;
794*1157Smax.romanov@nginx.com         boolean readStatus = false;
795*1157Smax.romanov@nginx.com         boolean readHeaders = false;
796*1157Smax.romanov@nginx.com         String line = null;
797*1157Smax.romanov@nginx.com         while (!readHeaders) {
798*1157Smax.romanov@nginx.com             // On entering loop buffer will be empty and at the start of a new
799*1157Smax.romanov@nginx.com             // loop the buffer will have been fully read.
800*1157Smax.romanov@nginx.com             response.clear();
801*1157Smax.romanov@nginx.com             // Blocking read
802*1157Smax.romanov@nginx.com             Future<Integer> read = channel.read(response);
803*1157Smax.romanov@nginx.com             Integer bytesRead = read.get(timeout, TimeUnit.MILLISECONDS);
804*1157Smax.romanov@nginx.com             if (bytesRead.intValue() == -1) {
805*1157Smax.romanov@nginx.com                 throw new EOFException();
806*1157Smax.romanov@nginx.com             }
807*1157Smax.romanov@nginx.com             response.flip();
808*1157Smax.romanov@nginx.com             while (response.hasRemaining() && !readHeaders) {
809*1157Smax.romanov@nginx.com                 if (line == null) {
810*1157Smax.romanov@nginx.com                     line = readLine(response);
811*1157Smax.romanov@nginx.com                 } else {
812*1157Smax.romanov@nginx.com                     line += readLine(response);
813*1157Smax.romanov@nginx.com                 }
814*1157Smax.romanov@nginx.com                 if ("\r\n".equals(line)) {
815*1157Smax.romanov@nginx.com                     readHeaders = true;
816*1157Smax.romanov@nginx.com                 } else if (line.endsWith("\r\n")) {
817*1157Smax.romanov@nginx.com                     if (readStatus) {
818*1157Smax.romanov@nginx.com                         parseHeaders(line, headers);
819*1157Smax.romanov@nginx.com                     } else {
820*1157Smax.romanov@nginx.com                         status = parseStatus(line);
821*1157Smax.romanov@nginx.com                         readStatus = true;
822*1157Smax.romanov@nginx.com                     }
823*1157Smax.romanov@nginx.com                     line = null;
824*1157Smax.romanov@nginx.com                 }
825*1157Smax.romanov@nginx.com             }
826*1157Smax.romanov@nginx.com         }
827*1157Smax.romanov@nginx.com 
828*1157Smax.romanov@nginx.com         return new HttpResponse(status, new WsHandshakeResponse(headers));
829*1157Smax.romanov@nginx.com     }
830*1157Smax.romanov@nginx.com 
831*1157Smax.romanov@nginx.com 
parseStatus(String line)832*1157Smax.romanov@nginx.com     private int parseStatus(String line) throws DeploymentException {
833*1157Smax.romanov@nginx.com         // This client only understands HTTP 1.
834*1157Smax.romanov@nginx.com         // RFC2616 is case specific
835*1157Smax.romanov@nginx.com         String[] parts = line.trim().split(" ");
836*1157Smax.romanov@nginx.com         // CONNECT for proxy may return a 1.0 response
837*1157Smax.romanov@nginx.com         if (parts.length < 2 || !("HTTP/1.0".equals(parts[0]) || "HTTP/1.1".equals(parts[0]))) {
838*1157Smax.romanov@nginx.com             throw new DeploymentException(sm.getString(
839*1157Smax.romanov@nginx.com                     "wsWebSocketContainer.invalidStatus", line));
840*1157Smax.romanov@nginx.com         }
841*1157Smax.romanov@nginx.com         try {
842*1157Smax.romanov@nginx.com             return Integer.parseInt(parts[1]);
843*1157Smax.romanov@nginx.com         } catch (NumberFormatException nfe) {
844*1157Smax.romanov@nginx.com             throw new DeploymentException(sm.getString(
845*1157Smax.romanov@nginx.com                     "wsWebSocketContainer.invalidStatus", line));
846*1157Smax.romanov@nginx.com         }
847*1157Smax.romanov@nginx.com     }
848*1157Smax.romanov@nginx.com 
849*1157Smax.romanov@nginx.com 
parseHeaders(String line, Map<String,List<String>> headers)850*1157Smax.romanov@nginx.com     private void parseHeaders(String line, Map<String,List<String>> headers) {
851*1157Smax.romanov@nginx.com         // Treat headers as single values by default.
852*1157Smax.romanov@nginx.com 
853*1157Smax.romanov@nginx.com         int index = line.indexOf(':');
854*1157Smax.romanov@nginx.com         if (index == -1) {
855*1157Smax.romanov@nginx.com             log.warn(sm.getString("wsWebSocketContainer.invalidHeader", line));
856*1157Smax.romanov@nginx.com             return;
857*1157Smax.romanov@nginx.com         }
858*1157Smax.romanov@nginx.com         // Header names are case insensitive so always use lower case
859*1157Smax.romanov@nginx.com         String headerName = line.substring(0, index).trim().toLowerCase(Locale.ENGLISH);
860*1157Smax.romanov@nginx.com         // Multi-value headers are stored as a single header and the client is
861*1157Smax.romanov@nginx.com         // expected to handle splitting into individual values
862*1157Smax.romanov@nginx.com         String headerValue = line.substring(index + 1).trim();
863*1157Smax.romanov@nginx.com 
864*1157Smax.romanov@nginx.com         List<String> values = headers.get(headerName);
865*1157Smax.romanov@nginx.com         if (values == null) {
866*1157Smax.romanov@nginx.com             values = new ArrayList<>(1);
867*1157Smax.romanov@nginx.com             headers.put(headerName, values);
868*1157Smax.romanov@nginx.com         }
869*1157Smax.romanov@nginx.com         values.add(headerValue);
870*1157Smax.romanov@nginx.com     }
871*1157Smax.romanov@nginx.com 
readLine(ByteBuffer response)872*1157Smax.romanov@nginx.com     private String readLine(ByteBuffer response) {
873*1157Smax.romanov@nginx.com         // All ISO-8859-1
874*1157Smax.romanov@nginx.com         StringBuilder sb = new StringBuilder();
875*1157Smax.romanov@nginx.com 
876*1157Smax.romanov@nginx.com         char c = 0;
877*1157Smax.romanov@nginx.com         while (response.hasRemaining()) {
878*1157Smax.romanov@nginx.com             c = (char) response.get();
879*1157Smax.romanov@nginx.com             sb.append(c);
880*1157Smax.romanov@nginx.com             if (c == 10) {
881*1157Smax.romanov@nginx.com                 break;
882*1157Smax.romanov@nginx.com             }
883*1157Smax.romanov@nginx.com         }
884*1157Smax.romanov@nginx.com 
885*1157Smax.romanov@nginx.com         return sb.toString();
886*1157Smax.romanov@nginx.com     }
887*1157Smax.romanov@nginx.com 
888*1157Smax.romanov@nginx.com 
createSSLEngine(Map<String,Object> userProperties, String host, int port)889*1157Smax.romanov@nginx.com     private SSLEngine createSSLEngine(Map<String,Object> userProperties, String host, int port)
890*1157Smax.romanov@nginx.com             throws DeploymentException {
891*1157Smax.romanov@nginx.com 
892*1157Smax.romanov@nginx.com         try {
893*1157Smax.romanov@nginx.com             // See if a custom SSLContext has been provided
894*1157Smax.romanov@nginx.com             SSLContext sslContext =
895*1157Smax.romanov@nginx.com                     (SSLContext) userProperties.get(Constants.SSL_CONTEXT_PROPERTY);
896*1157Smax.romanov@nginx.com 
897*1157Smax.romanov@nginx.com             if (sslContext == null) {
898*1157Smax.romanov@nginx.com                 // Create the SSL Context
899*1157Smax.romanov@nginx.com                 sslContext = SSLContext.getInstance("TLS");
900*1157Smax.romanov@nginx.com 
901*1157Smax.romanov@nginx.com                 // Trust store
902*1157Smax.romanov@nginx.com                 String sslTrustStoreValue =
903*1157Smax.romanov@nginx.com                         (String) userProperties.get(Constants.SSL_TRUSTSTORE_PROPERTY);
904*1157Smax.romanov@nginx.com                 if (sslTrustStoreValue != null) {
905*1157Smax.romanov@nginx.com                     String sslTrustStorePwdValue = (String) userProperties.get(
906*1157Smax.romanov@nginx.com                             Constants.SSL_TRUSTSTORE_PWD_PROPERTY);
907*1157Smax.romanov@nginx.com                     if (sslTrustStorePwdValue == null) {
908*1157Smax.romanov@nginx.com                         sslTrustStorePwdValue = Constants.SSL_TRUSTSTORE_PWD_DEFAULT;
909*1157Smax.romanov@nginx.com                     }
910*1157Smax.romanov@nginx.com 
911*1157Smax.romanov@nginx.com                     File keyStoreFile = new File(sslTrustStoreValue);
912*1157Smax.romanov@nginx.com                     KeyStore ks = KeyStore.getInstance("JKS");
913*1157Smax.romanov@nginx.com                     try (InputStream is = new FileInputStream(keyStoreFile)) {
914*1157Smax.romanov@nginx.com                         ks.load(is, sslTrustStorePwdValue.toCharArray());
915*1157Smax.romanov@nginx.com                     }
916*1157Smax.romanov@nginx.com 
917*1157Smax.romanov@nginx.com                     TrustManagerFactory tmf = TrustManagerFactory.getInstance(
918*1157Smax.romanov@nginx.com                             TrustManagerFactory.getDefaultAlgorithm());
919*1157Smax.romanov@nginx.com                     tmf.init(ks);
920*1157Smax.romanov@nginx.com 
921*1157Smax.romanov@nginx.com                     sslContext.init(null, tmf.getTrustManagers(), null);
922*1157Smax.romanov@nginx.com                 } else {
923*1157Smax.romanov@nginx.com                     sslContext.init(null, null, null);
924*1157Smax.romanov@nginx.com                 }
925*1157Smax.romanov@nginx.com             }
926*1157Smax.romanov@nginx.com 
927*1157Smax.romanov@nginx.com             SSLEngine engine = sslContext.createSSLEngine(host, port);
928*1157Smax.romanov@nginx.com 
929*1157Smax.romanov@nginx.com             String sslProtocolsValue =
930*1157Smax.romanov@nginx.com                     (String) userProperties.get(Constants.SSL_PROTOCOLS_PROPERTY);
931*1157Smax.romanov@nginx.com             if (sslProtocolsValue != null) {
932*1157Smax.romanov@nginx.com                 engine.setEnabledProtocols(sslProtocolsValue.split(","));
933*1157Smax.romanov@nginx.com             }
934*1157Smax.romanov@nginx.com 
935*1157Smax.romanov@nginx.com             engine.setUseClientMode(true);
936*1157Smax.romanov@nginx.com 
937*1157Smax.romanov@nginx.com             // Enable host verification
938*1157Smax.romanov@nginx.com             // Start with current settings (returns a copy)
939*1157Smax.romanov@nginx.com             SSLParameters sslParams = engine.getSSLParameters();
940*1157Smax.romanov@nginx.com             // Use HTTPS since WebSocket starts over HTTP(S)
941*1157Smax.romanov@nginx.com             sslParams.setEndpointIdentificationAlgorithm("HTTPS");
942*1157Smax.romanov@nginx.com             // Write the parameters back
943*1157Smax.romanov@nginx.com             engine.setSSLParameters(sslParams);
944*1157Smax.romanov@nginx.com 
945*1157Smax.romanov@nginx.com             return engine;
946*1157Smax.romanov@nginx.com         } catch (Exception e) {
947*1157Smax.romanov@nginx.com             throw new DeploymentException(sm.getString(
948*1157Smax.romanov@nginx.com                     "wsWebSocketContainer.sslEngineFail"), e);
949*1157Smax.romanov@nginx.com         }
950*1157Smax.romanov@nginx.com     }
951*1157Smax.romanov@nginx.com 
952*1157Smax.romanov@nginx.com 
953*1157Smax.romanov@nginx.com     @Override
getDefaultMaxSessionIdleTimeout()954*1157Smax.romanov@nginx.com     public long getDefaultMaxSessionIdleTimeout() {
955*1157Smax.romanov@nginx.com         return defaultMaxSessionIdleTimeout;
956*1157Smax.romanov@nginx.com     }
957*1157Smax.romanov@nginx.com 
958*1157Smax.romanov@nginx.com 
959*1157Smax.romanov@nginx.com     @Override
setDefaultMaxSessionIdleTimeout(long timeout)960*1157Smax.romanov@nginx.com     public void setDefaultMaxSessionIdleTimeout(long timeout) {
961*1157Smax.romanov@nginx.com         this.defaultMaxSessionIdleTimeout = timeout;
962*1157Smax.romanov@nginx.com     }
963*1157Smax.romanov@nginx.com 
964*1157Smax.romanov@nginx.com 
965*1157Smax.romanov@nginx.com     @Override
getDefaultMaxBinaryMessageBufferSize()966*1157Smax.romanov@nginx.com     public int getDefaultMaxBinaryMessageBufferSize() {
967*1157Smax.romanov@nginx.com         return maxBinaryMessageBufferSize;
968*1157Smax.romanov@nginx.com     }
969*1157Smax.romanov@nginx.com 
970*1157Smax.romanov@nginx.com 
971*1157Smax.romanov@nginx.com     @Override
setDefaultMaxBinaryMessageBufferSize(int max)972*1157Smax.romanov@nginx.com     public void setDefaultMaxBinaryMessageBufferSize(int max) {
973*1157Smax.romanov@nginx.com         maxBinaryMessageBufferSize = max;
974*1157Smax.romanov@nginx.com     }
975*1157Smax.romanov@nginx.com 
976*1157Smax.romanov@nginx.com 
977*1157Smax.romanov@nginx.com     @Override
getDefaultMaxTextMessageBufferSize()978*1157Smax.romanov@nginx.com     public int getDefaultMaxTextMessageBufferSize() {
979*1157Smax.romanov@nginx.com         return maxTextMessageBufferSize;
980*1157Smax.romanov@nginx.com     }
981*1157Smax.romanov@nginx.com 
982*1157Smax.romanov@nginx.com 
983*1157Smax.romanov@nginx.com     @Override
setDefaultMaxTextMessageBufferSize(int max)984*1157Smax.romanov@nginx.com     public void setDefaultMaxTextMessageBufferSize(int max) {
985*1157Smax.romanov@nginx.com         maxTextMessageBufferSize = max;
986*1157Smax.romanov@nginx.com     }
987*1157Smax.romanov@nginx.com 
988*1157Smax.romanov@nginx.com 
989*1157Smax.romanov@nginx.com     /**
990*1157Smax.romanov@nginx.com      * {@inheritDoc}
991*1157Smax.romanov@nginx.com      *
992*1157Smax.romanov@nginx.com      * Currently, this implementation does not support any extensions.
993*1157Smax.romanov@nginx.com      */
994*1157Smax.romanov@nginx.com     @Override
getInstalledExtensions()995*1157Smax.romanov@nginx.com     public Set<Extension> getInstalledExtensions() {
996*1157Smax.romanov@nginx.com         return Collections.emptySet();
997*1157Smax.romanov@nginx.com     }
998*1157Smax.romanov@nginx.com 
999*1157Smax.romanov@nginx.com 
1000*1157Smax.romanov@nginx.com     /**
1001*1157Smax.romanov@nginx.com      * {@inheritDoc}
1002*1157Smax.romanov@nginx.com      *
1003*1157Smax.romanov@nginx.com      * The default value for this implementation is -1.
1004*1157Smax.romanov@nginx.com      */
1005*1157Smax.romanov@nginx.com     @Override
getDefaultAsyncSendTimeout()1006*1157Smax.romanov@nginx.com     public long getDefaultAsyncSendTimeout() {
1007*1157Smax.romanov@nginx.com         return defaultAsyncTimeout;
1008*1157Smax.romanov@nginx.com     }
1009*1157Smax.romanov@nginx.com 
1010*1157Smax.romanov@nginx.com 
1011*1157Smax.romanov@nginx.com     /**
1012*1157Smax.romanov@nginx.com      * {@inheritDoc}
1013*1157Smax.romanov@nginx.com      *
1014*1157Smax.romanov@nginx.com      * The default value for this implementation is -1.
1015*1157Smax.romanov@nginx.com      */
1016*1157Smax.romanov@nginx.com     @Override
setAsyncSendTimeout(long timeout)1017*1157Smax.romanov@nginx.com     public void setAsyncSendTimeout(long timeout) {
1018*1157Smax.romanov@nginx.com         this.defaultAsyncTimeout = timeout;
1019*1157Smax.romanov@nginx.com     }
1020*1157Smax.romanov@nginx.com 
1021*1157Smax.romanov@nginx.com 
1022*1157Smax.romanov@nginx.com     /**
1023*1157Smax.romanov@nginx.com      * Cleans up the resources still in use by WebSocket sessions created from
1024*1157Smax.romanov@nginx.com      * this container. This includes closing sessions and cancelling
1025*1157Smax.romanov@nginx.com      * {@link Future}s associated with blocking read/writes.
1026*1157Smax.romanov@nginx.com      */
destroy()1027*1157Smax.romanov@nginx.com     public void destroy() {
1028*1157Smax.romanov@nginx.com         CloseReason cr = new CloseReason(
1029*1157Smax.romanov@nginx.com                 CloseCodes.GOING_AWAY, sm.getString("wsWebSocketContainer.shutdown"));
1030*1157Smax.romanov@nginx.com 
1031*1157Smax.romanov@nginx.com         for (WsSession session : sessions.keySet()) {
1032*1157Smax.romanov@nginx.com             try {
1033*1157Smax.romanov@nginx.com                 session.close(cr);
1034*1157Smax.romanov@nginx.com             } catch (IOException ioe) {
1035*1157Smax.romanov@nginx.com                 log.debug(sm.getString(
1036*1157Smax.romanov@nginx.com                         "wsWebSocketContainer.sessionCloseFail", session.getId()), ioe);
1037*1157Smax.romanov@nginx.com             }
1038*1157Smax.romanov@nginx.com         }
1039*1157Smax.romanov@nginx.com 
1040*1157Smax.romanov@nginx.com         // Only unregister with AsyncChannelGroupUtil if this instance
1041*1157Smax.romanov@nginx.com         // registered with it
1042*1157Smax.romanov@nginx.com         if (asynchronousChannelGroup != null) {
1043*1157Smax.romanov@nginx.com             synchronized (asynchronousChannelGroupLock) {
1044*1157Smax.romanov@nginx.com                 if (asynchronousChannelGroup != null) {
1045*1157Smax.romanov@nginx.com                     AsyncChannelGroupUtil.unregister();
1046*1157Smax.romanov@nginx.com                     asynchronousChannelGroup = null;
1047*1157Smax.romanov@nginx.com                 }
1048*1157Smax.romanov@nginx.com             }
1049*1157Smax.romanov@nginx.com         }
1050*1157Smax.romanov@nginx.com     }
1051*1157Smax.romanov@nginx.com 
1052*1157Smax.romanov@nginx.com 
getAsynchronousChannelGroup()1053*1157Smax.romanov@nginx.com     private AsynchronousChannelGroup getAsynchronousChannelGroup() {
1054*1157Smax.romanov@nginx.com         // Use AsyncChannelGroupUtil to share a common group amongst all
1055*1157Smax.romanov@nginx.com         // WebSocket clients
1056*1157Smax.romanov@nginx.com         AsynchronousChannelGroup result = asynchronousChannelGroup;
1057*1157Smax.romanov@nginx.com         if (result == null) {
1058*1157Smax.romanov@nginx.com             synchronized (asynchronousChannelGroupLock) {
1059*1157Smax.romanov@nginx.com                 if (asynchronousChannelGroup == null) {
1060*1157Smax.romanov@nginx.com                     asynchronousChannelGroup = AsyncChannelGroupUtil.register();
1061*1157Smax.romanov@nginx.com                 }
1062*1157Smax.romanov@nginx.com                 result = asynchronousChannelGroup;
1063*1157Smax.romanov@nginx.com             }
1064*1157Smax.romanov@nginx.com         }
1065*1157Smax.romanov@nginx.com         return result;
1066*1157Smax.romanov@nginx.com     }
1067*1157Smax.romanov@nginx.com 
1068*1157Smax.romanov@nginx.com 
1069*1157Smax.romanov@nginx.com     // ----------------------------------------------- BackgroundProcess methods
1070*1157Smax.romanov@nginx.com 
1071*1157Smax.romanov@nginx.com     @Override
backgroundProcess()1072*1157Smax.romanov@nginx.com     public void backgroundProcess() {
1073*1157Smax.romanov@nginx.com         // This method gets called once a second.
1074*1157Smax.romanov@nginx.com         backgroundProcessCount ++;
1075*1157Smax.romanov@nginx.com         if (backgroundProcessCount >= processPeriod) {
1076*1157Smax.romanov@nginx.com             backgroundProcessCount = 0;
1077*1157Smax.romanov@nginx.com 
1078*1157Smax.romanov@nginx.com             for (WsSession wsSession : sessions.keySet()) {
1079*1157Smax.romanov@nginx.com                 wsSession.checkExpiration();
1080*1157Smax.romanov@nginx.com             }
1081*1157Smax.romanov@nginx.com         }
1082*1157Smax.romanov@nginx.com 
1083*1157Smax.romanov@nginx.com     }
1084*1157Smax.romanov@nginx.com 
1085*1157Smax.romanov@nginx.com 
1086*1157Smax.romanov@nginx.com     @Override
setProcessPeriod(int period)1087*1157Smax.romanov@nginx.com     public void setProcessPeriod(int period) {
1088*1157Smax.romanov@nginx.com         this.processPeriod = period;
1089*1157Smax.romanov@nginx.com     }
1090*1157Smax.romanov@nginx.com 
1091*1157Smax.romanov@nginx.com 
1092*1157Smax.romanov@nginx.com     /**
1093*1157Smax.romanov@nginx.com      * {@inheritDoc}
1094*1157Smax.romanov@nginx.com      *
1095*1157Smax.romanov@nginx.com      * The default value is 10 which means session expirations are processed
1096*1157Smax.romanov@nginx.com      * every 10 seconds.
1097*1157Smax.romanov@nginx.com      */
1098*1157Smax.romanov@nginx.com     @Override
getProcessPeriod()1099*1157Smax.romanov@nginx.com     public int getProcessPeriod() {
1100*1157Smax.romanov@nginx.com         return processPeriod;
1101*1157Smax.romanov@nginx.com     }
1102*1157Smax.romanov@nginx.com 
1103*1157Smax.romanov@nginx.com 
1104*1157Smax.romanov@nginx.com     private static class HttpResponse {
1105*1157Smax.romanov@nginx.com         private final int status;
1106*1157Smax.romanov@nginx.com         private final HandshakeResponse handshakeResponse;
1107*1157Smax.romanov@nginx.com 
HttpResponse(int status, HandshakeResponse handshakeResponse)1108*1157Smax.romanov@nginx.com         public HttpResponse(int status, HandshakeResponse handshakeResponse) {
1109*1157Smax.romanov@nginx.com             this.status = status;
1110*1157Smax.romanov@nginx.com             this.handshakeResponse = handshakeResponse;
1111*1157Smax.romanov@nginx.com         }
1112*1157Smax.romanov@nginx.com 
1113*1157Smax.romanov@nginx.com 
getStatus()1114*1157Smax.romanov@nginx.com         public int getStatus() {
1115*1157Smax.romanov@nginx.com             return status;
1116*1157Smax.romanov@nginx.com         }
1117*1157Smax.romanov@nginx.com 
1118*1157Smax.romanov@nginx.com 
getHandshakeResponse()1119*1157Smax.romanov@nginx.com         public HandshakeResponse getHandshakeResponse() {
1120*1157Smax.romanov@nginx.com             return handshakeResponse;
1121*1157Smax.romanov@nginx.com         }
1122*1157Smax.romanov@nginx.com     }
1123*1157Smax.romanov@nginx.com }
1124