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