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.IOException; 20*1157Smax.romanov@nginx.com import java.net.URI; 21*1157Smax.romanov@nginx.com import java.nio.ByteBuffer; 22*1157Smax.romanov@nginx.com import java.nio.ByteOrder; 23*1157Smax.romanov@nginx.com import java.nio.CharBuffer; 24*1157Smax.romanov@nginx.com import java.nio.channels.WritePendingException; 25*1157Smax.romanov@nginx.com import java.nio.charset.CharsetDecoder; 26*1157Smax.romanov@nginx.com import java.nio.charset.CoderResult; 27*1157Smax.romanov@nginx.com import java.nio.charset.CodingErrorAction; 28*1157Smax.romanov@nginx.com import java.nio.charset.StandardCharsets; 29*1157Smax.romanov@nginx.com import java.security.Principal; 30*1157Smax.romanov@nginx.com import java.util.Collections; 31*1157Smax.romanov@nginx.com import java.util.HashSet; 32*1157Smax.romanov@nginx.com import java.util.List; 33*1157Smax.romanov@nginx.com import java.util.Map; 34*1157Smax.romanov@nginx.com import java.util.Set; 35*1157Smax.romanov@nginx.com import java.util.concurrent.ConcurrentHashMap; 36*1157Smax.romanov@nginx.com import java.util.concurrent.atomic.AtomicLong; 37*1157Smax.romanov@nginx.com 38*1157Smax.romanov@nginx.com import javax.websocket.CloseReason; 39*1157Smax.romanov@nginx.com import javax.websocket.CloseReason.CloseCode; 40*1157Smax.romanov@nginx.com import javax.websocket.CloseReason.CloseCodes; 41*1157Smax.romanov@nginx.com import javax.websocket.DeploymentException; 42*1157Smax.romanov@nginx.com import javax.websocket.Endpoint; 43*1157Smax.romanov@nginx.com import javax.websocket.EndpointConfig; 44*1157Smax.romanov@nginx.com import javax.websocket.Extension; 45*1157Smax.romanov@nginx.com import javax.websocket.MessageHandler; 46*1157Smax.romanov@nginx.com import javax.websocket.MessageHandler.Partial; 47*1157Smax.romanov@nginx.com import javax.websocket.MessageHandler.Whole; 48*1157Smax.romanov@nginx.com import javax.websocket.PongMessage; 49*1157Smax.romanov@nginx.com import javax.websocket.RemoteEndpoint; 50*1157Smax.romanov@nginx.com import javax.websocket.SendResult; 51*1157Smax.romanov@nginx.com import javax.websocket.Session; 52*1157Smax.romanov@nginx.com import javax.websocket.WebSocketContainer; 53*1157Smax.romanov@nginx.com 54*1157Smax.romanov@nginx.com import org.apache.juli.logging.Log; 55*1157Smax.romanov@nginx.com import org.apache.juli.logging.LogFactory; 56*1157Smax.romanov@nginx.com import org.apache.tomcat.InstanceManager; 57*1157Smax.romanov@nginx.com import org.apache.tomcat.InstanceManagerBindings; 58*1157Smax.romanov@nginx.com import org.apache.tomcat.util.ExceptionUtils; 59*1157Smax.romanov@nginx.com import org.apache.tomcat.util.buf.Utf8Decoder; 60*1157Smax.romanov@nginx.com import org.apache.tomcat.util.res.StringManager; 61*1157Smax.romanov@nginx.com 62*1157Smax.romanov@nginx.com import nginx.unit.Request; 63*1157Smax.romanov@nginx.com 64*1157Smax.romanov@nginx.com public class WsSession implements Session { 65*1157Smax.romanov@nginx.com 66*1157Smax.romanov@nginx.com // An ellipsis is a single character that looks like three periods in a row 67*1157Smax.romanov@nginx.com // and is used to indicate a continuation. 68*1157Smax.romanov@nginx.com private static final byte[] ELLIPSIS_BYTES = "\u2026".getBytes(StandardCharsets.UTF_8); 69*1157Smax.romanov@nginx.com // An ellipsis is three bytes in UTF-8 70*1157Smax.romanov@nginx.com private static final int ELLIPSIS_BYTES_LEN = ELLIPSIS_BYTES.length; 71*1157Smax.romanov@nginx.com 72*1157Smax.romanov@nginx.com private static final StringManager sm = StringManager.getManager(WsSession.class); 73*1157Smax.romanov@nginx.com private static AtomicLong ids = new AtomicLong(0); 74*1157Smax.romanov@nginx.com 75*1157Smax.romanov@nginx.com private final Log log = LogFactory.getLog(WsSession.class); // must not be static 76*1157Smax.romanov@nginx.com 77*1157Smax.romanov@nginx.com private final CharsetDecoder utf8DecoderMessage = new Utf8Decoder(). 78*1157Smax.romanov@nginx.com onMalformedInput(CodingErrorAction.REPORT). 79*1157Smax.romanov@nginx.com onUnmappableCharacter(CodingErrorAction.REPORT); 80*1157Smax.romanov@nginx.com 81*1157Smax.romanov@nginx.com private final Endpoint localEndpoint; 82*1157Smax.romanov@nginx.com private final WsRemoteEndpointImplBase wsRemoteEndpoint; 83*1157Smax.romanov@nginx.com private final RemoteEndpoint.Async remoteEndpointAsync; 84*1157Smax.romanov@nginx.com private final RemoteEndpoint.Basic remoteEndpointBasic; 85*1157Smax.romanov@nginx.com private final ClassLoader applicationClassLoader; 86*1157Smax.romanov@nginx.com private final WsWebSocketContainer webSocketContainer; 87*1157Smax.romanov@nginx.com private final URI requestUri; 88*1157Smax.romanov@nginx.com private final Map<String, List<String>> requestParameterMap; 89*1157Smax.romanov@nginx.com private final String queryString; 90*1157Smax.romanov@nginx.com private final Principal userPrincipal; 91*1157Smax.romanov@nginx.com private final EndpointConfig endpointConfig; 92*1157Smax.romanov@nginx.com 93*1157Smax.romanov@nginx.com private final List<Extension> negotiatedExtensions; 94*1157Smax.romanov@nginx.com private final String subProtocol; 95*1157Smax.romanov@nginx.com private final Map<String, String> pathParameters; 96*1157Smax.romanov@nginx.com private final boolean secure; 97*1157Smax.romanov@nginx.com private final String httpSessionId; 98*1157Smax.romanov@nginx.com private final String id; 99*1157Smax.romanov@nginx.com 100*1157Smax.romanov@nginx.com // Expected to handle message types of <String> only 101*1157Smax.romanov@nginx.com private volatile MessageHandler textMessageHandler = null; 102*1157Smax.romanov@nginx.com // Expected to handle message types of <ByteBuffer> only 103*1157Smax.romanov@nginx.com private volatile MessageHandler binaryMessageHandler = null; 104*1157Smax.romanov@nginx.com private volatile MessageHandler.Whole<PongMessage> pongMessageHandler = null; 105*1157Smax.romanov@nginx.com private volatile State state = State.OPEN; 106*1157Smax.romanov@nginx.com private final Object stateLock = new Object(); 107*1157Smax.romanov@nginx.com private final Map<String, Object> userProperties = new ConcurrentHashMap<>(); 108*1157Smax.romanov@nginx.com private volatile int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE; 109*1157Smax.romanov@nginx.com private volatile int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE; 110*1157Smax.romanov@nginx.com private volatile long maxIdleTimeout = 0; 111*1157Smax.romanov@nginx.com private volatile long lastActive = System.currentTimeMillis(); 112*1157Smax.romanov@nginx.com private Map<FutureToSendHandler, FutureToSendHandler> futures = new ConcurrentHashMap<>(); 113*1157Smax.romanov@nginx.com 114*1157Smax.romanov@nginx.com private CharBuffer messageBufferText; 115*1157Smax.romanov@nginx.com private ByteBuffer binaryBuffer; 116*1157Smax.romanov@nginx.com private byte startOpCode = Constants.OPCODE_CONTINUATION; 117*1157Smax.romanov@nginx.com 118*1157Smax.romanov@nginx.com /** 119*1157Smax.romanov@nginx.com * Creates a new WebSocket session for communication between the two 120*1157Smax.romanov@nginx.com * provided end points. The result of {@link Thread#getContextClassLoader()} 121*1157Smax.romanov@nginx.com * at the time this constructor is called will be used when calling 122*1157Smax.romanov@nginx.com * {@link Endpoint#onClose(Session, CloseReason)}. 123*1157Smax.romanov@nginx.com * 124*1157Smax.romanov@nginx.com * @param localEndpoint The end point managed by this code 125*1157Smax.romanov@nginx.com * @param wsRemoteEndpoint The other / remote endpoint 126*1157Smax.romanov@nginx.com * @param wsWebSocketContainer The container that created this session 127*1157Smax.romanov@nginx.com * @param requestUri The URI used to connect to this endpoint or 128*1157Smax.romanov@nginx.com * <code>null</code> is this is a client session 129*1157Smax.romanov@nginx.com * @param requestParameterMap The parameters associated with the request 130*1157Smax.romanov@nginx.com * that initiated this session or 131*1157Smax.romanov@nginx.com * <code>null</code> if this is a client session 132*1157Smax.romanov@nginx.com * @param queryString The query string associated with the request 133*1157Smax.romanov@nginx.com * that initiated this session or 134*1157Smax.romanov@nginx.com * <code>null</code> if this is a client session 135*1157Smax.romanov@nginx.com * @param userPrincipal The principal associated with the request 136*1157Smax.romanov@nginx.com * that initiated this session or 137*1157Smax.romanov@nginx.com * <code>null</code> if this is a client session 138*1157Smax.romanov@nginx.com * @param httpSessionId The HTTP session ID associated with the 139*1157Smax.romanov@nginx.com * request that initiated this session or 140*1157Smax.romanov@nginx.com * <code>null</code> if this is a client session 141*1157Smax.romanov@nginx.com * @param negotiatedExtensions The agreed extensions to use for this session 142*1157Smax.romanov@nginx.com * @param subProtocol The agreed subprotocol to use for this 143*1157Smax.romanov@nginx.com * session 144*1157Smax.romanov@nginx.com * @param pathParameters The path parameters associated with the 145*1157Smax.romanov@nginx.com * request that initiated this session or 146*1157Smax.romanov@nginx.com * <code>null</code> if this is a client session 147*1157Smax.romanov@nginx.com * @param secure Was this session initiated over a secure 148*1157Smax.romanov@nginx.com * connection? 149*1157Smax.romanov@nginx.com * @param endpointConfig The configuration information for the 150*1157Smax.romanov@nginx.com * endpoint 151*1157Smax.romanov@nginx.com * @throws DeploymentException if an invalid encode is specified 152*1157Smax.romanov@nginx.com */ WsSession(Endpoint localEndpoint, WsRemoteEndpointImplBase wsRemoteEndpoint, WsWebSocketContainer wsWebSocketContainer, URI requestUri, Map<String, List<String>> requestParameterMap, String queryString, Principal userPrincipal, String httpSessionId, List<Extension> negotiatedExtensions, String subProtocol, Map<String, String> pathParameters, boolean secure, EndpointConfig endpointConfig, Request request)153*1157Smax.romanov@nginx.com public WsSession(Endpoint localEndpoint, 154*1157Smax.romanov@nginx.com WsRemoteEndpointImplBase wsRemoteEndpoint, 155*1157Smax.romanov@nginx.com WsWebSocketContainer wsWebSocketContainer, 156*1157Smax.romanov@nginx.com URI requestUri, Map<String, List<String>> requestParameterMap, 157*1157Smax.romanov@nginx.com String queryString, Principal userPrincipal, String httpSessionId, 158*1157Smax.romanov@nginx.com List<Extension> negotiatedExtensions, String subProtocol, Map<String, String> pathParameters, 159*1157Smax.romanov@nginx.com boolean secure, EndpointConfig endpointConfig, 160*1157Smax.romanov@nginx.com Request request) throws DeploymentException { 161*1157Smax.romanov@nginx.com this.localEndpoint = localEndpoint; 162*1157Smax.romanov@nginx.com this.wsRemoteEndpoint = wsRemoteEndpoint; 163*1157Smax.romanov@nginx.com this.wsRemoteEndpoint.setSession(this); 164*1157Smax.romanov@nginx.com this.wsRemoteEndpoint.setRequest(request); 165*1157Smax.romanov@nginx.com 166*1157Smax.romanov@nginx.com request.setWsSession(this); 167*1157Smax.romanov@nginx.com 168*1157Smax.romanov@nginx.com this.remoteEndpointAsync = new WsRemoteEndpointAsync(wsRemoteEndpoint); 169*1157Smax.romanov@nginx.com this.remoteEndpointBasic = new WsRemoteEndpointBasic(wsRemoteEndpoint); 170*1157Smax.romanov@nginx.com this.webSocketContainer = wsWebSocketContainer; 171*1157Smax.romanov@nginx.com applicationClassLoader = Thread.currentThread().getContextClassLoader(); 172*1157Smax.romanov@nginx.com wsRemoteEndpoint.setSendTimeout(wsWebSocketContainer.getDefaultAsyncSendTimeout()); 173*1157Smax.romanov@nginx.com this.maxBinaryMessageBufferSize = webSocketContainer.getDefaultMaxBinaryMessageBufferSize(); 174*1157Smax.romanov@nginx.com this.maxTextMessageBufferSize = webSocketContainer.getDefaultMaxTextMessageBufferSize(); 175*1157Smax.romanov@nginx.com this.maxIdleTimeout = webSocketContainer.getDefaultMaxSessionIdleTimeout(); 176*1157Smax.romanov@nginx.com this.requestUri = requestUri; 177*1157Smax.romanov@nginx.com if (requestParameterMap == null) { 178*1157Smax.romanov@nginx.com this.requestParameterMap = Collections.emptyMap(); 179*1157Smax.romanov@nginx.com } else { 180*1157Smax.romanov@nginx.com this.requestParameterMap = requestParameterMap; 181*1157Smax.romanov@nginx.com } 182*1157Smax.romanov@nginx.com this.queryString = queryString; 183*1157Smax.romanov@nginx.com this.userPrincipal = userPrincipal; 184*1157Smax.romanov@nginx.com this.httpSessionId = httpSessionId; 185*1157Smax.romanov@nginx.com this.negotiatedExtensions = negotiatedExtensions; 186*1157Smax.romanov@nginx.com if (subProtocol == null) { 187*1157Smax.romanov@nginx.com this.subProtocol = ""; 188*1157Smax.romanov@nginx.com } else { 189*1157Smax.romanov@nginx.com this.subProtocol = subProtocol; 190*1157Smax.romanov@nginx.com } 191*1157Smax.romanov@nginx.com this.pathParameters = pathParameters; 192*1157Smax.romanov@nginx.com this.secure = secure; 193*1157Smax.romanov@nginx.com this.wsRemoteEndpoint.setEncoders(endpointConfig); 194*1157Smax.romanov@nginx.com this.endpointConfig = endpointConfig; 195*1157Smax.romanov@nginx.com 196*1157Smax.romanov@nginx.com this.userProperties.putAll(endpointConfig.getUserProperties()); 197*1157Smax.romanov@nginx.com this.id = Long.toHexString(ids.getAndIncrement()); 198*1157Smax.romanov@nginx.com 199*1157Smax.romanov@nginx.com InstanceManager instanceManager = webSocketContainer.getInstanceManager(); 200*1157Smax.romanov@nginx.com if (instanceManager == null) { 201*1157Smax.romanov@nginx.com instanceManager = InstanceManagerBindings.get(applicationClassLoader); 202*1157Smax.romanov@nginx.com } 203*1157Smax.romanov@nginx.com if (instanceManager != null) { 204*1157Smax.romanov@nginx.com try { 205*1157Smax.romanov@nginx.com instanceManager.newInstance(localEndpoint); 206*1157Smax.romanov@nginx.com } catch (Exception e) { 207*1157Smax.romanov@nginx.com throw new DeploymentException(sm.getString("wsSession.instanceNew"), e); 208*1157Smax.romanov@nginx.com } 209*1157Smax.romanov@nginx.com } 210*1157Smax.romanov@nginx.com 211*1157Smax.romanov@nginx.com if (log.isDebugEnabled()) { 212*1157Smax.romanov@nginx.com log.debug(sm.getString("wsSession.created", id)); 213*1157Smax.romanov@nginx.com } 214*1157Smax.romanov@nginx.com 215*1157Smax.romanov@nginx.com messageBufferText = CharBuffer.allocate(maxTextMessageBufferSize); 216*1157Smax.romanov@nginx.com } 217*1157Smax.romanov@nginx.com wsSession_test()218*1157Smax.romanov@nginx.com public static String wsSession_test() { 219*1157Smax.romanov@nginx.com return sm.getString("wsSession.instanceNew"); 220*1157Smax.romanov@nginx.com } 221*1157Smax.romanov@nginx.com 222*1157Smax.romanov@nginx.com 223*1157Smax.romanov@nginx.com @Override getContainer()224*1157Smax.romanov@nginx.com public WebSocketContainer getContainer() { 225*1157Smax.romanov@nginx.com checkState(); 226*1157Smax.romanov@nginx.com return webSocketContainer; 227*1157Smax.romanov@nginx.com } 228*1157Smax.romanov@nginx.com 229*1157Smax.romanov@nginx.com 230*1157Smax.romanov@nginx.com @Override addMessageHandler(MessageHandler listener)231*1157Smax.romanov@nginx.com public void addMessageHandler(MessageHandler listener) { 232*1157Smax.romanov@nginx.com Class<?> target = Util.getMessageType(listener); 233*1157Smax.romanov@nginx.com doAddMessageHandler(target, listener); 234*1157Smax.romanov@nginx.com } 235*1157Smax.romanov@nginx.com 236*1157Smax.romanov@nginx.com 237*1157Smax.romanov@nginx.com @Override addMessageHandler(Class<T> clazz, Partial<T> handler)238*1157Smax.romanov@nginx.com public <T> void addMessageHandler(Class<T> clazz, Partial<T> handler) 239*1157Smax.romanov@nginx.com throws IllegalStateException { 240*1157Smax.romanov@nginx.com doAddMessageHandler(clazz, handler); 241*1157Smax.romanov@nginx.com } 242*1157Smax.romanov@nginx.com 243*1157Smax.romanov@nginx.com 244*1157Smax.romanov@nginx.com @Override addMessageHandler(Class<T> clazz, Whole<T> handler)245*1157Smax.romanov@nginx.com public <T> void addMessageHandler(Class<T> clazz, Whole<T> handler) 246*1157Smax.romanov@nginx.com throws IllegalStateException { 247*1157Smax.romanov@nginx.com doAddMessageHandler(clazz, handler); 248*1157Smax.romanov@nginx.com } 249*1157Smax.romanov@nginx.com 250*1157Smax.romanov@nginx.com 251*1157Smax.romanov@nginx.com @SuppressWarnings("unchecked") doAddMessageHandler(Class<?> target, MessageHandler listener)252*1157Smax.romanov@nginx.com private void doAddMessageHandler(Class<?> target, MessageHandler listener) { 253*1157Smax.romanov@nginx.com checkState(); 254*1157Smax.romanov@nginx.com 255*1157Smax.romanov@nginx.com // Message handlers that require decoders may map to text messages, 256*1157Smax.romanov@nginx.com // binary messages, both or neither. 257*1157Smax.romanov@nginx.com 258*1157Smax.romanov@nginx.com // The frame processing code expects binary message handlers to 259*1157Smax.romanov@nginx.com // accept ByteBuffer 260*1157Smax.romanov@nginx.com 261*1157Smax.romanov@nginx.com // Use the POJO message handler wrappers as they are designed to wrap 262*1157Smax.romanov@nginx.com // arbitrary objects with MessageHandlers and can wrap MessageHandlers 263*1157Smax.romanov@nginx.com // just as easily. 264*1157Smax.romanov@nginx.com 265*1157Smax.romanov@nginx.com Set<MessageHandlerResult> mhResults = Util.getMessageHandlers(target, listener, 266*1157Smax.romanov@nginx.com endpointConfig, this); 267*1157Smax.romanov@nginx.com 268*1157Smax.romanov@nginx.com for (MessageHandlerResult mhResult : mhResults) { 269*1157Smax.romanov@nginx.com switch (mhResult.getType()) { 270*1157Smax.romanov@nginx.com case TEXT: { 271*1157Smax.romanov@nginx.com if (textMessageHandler != null) { 272*1157Smax.romanov@nginx.com throw new IllegalStateException(sm.getString("wsSession.duplicateHandlerText")); 273*1157Smax.romanov@nginx.com } 274*1157Smax.romanov@nginx.com textMessageHandler = mhResult.getHandler(); 275*1157Smax.romanov@nginx.com break; 276*1157Smax.romanov@nginx.com } 277*1157Smax.romanov@nginx.com case BINARY: { 278*1157Smax.romanov@nginx.com if (binaryMessageHandler != null) { 279*1157Smax.romanov@nginx.com throw new IllegalStateException( 280*1157Smax.romanov@nginx.com sm.getString("wsSession.duplicateHandlerBinary")); 281*1157Smax.romanov@nginx.com } 282*1157Smax.romanov@nginx.com binaryMessageHandler = mhResult.getHandler(); 283*1157Smax.romanov@nginx.com break; 284*1157Smax.romanov@nginx.com } 285*1157Smax.romanov@nginx.com case PONG: { 286*1157Smax.romanov@nginx.com if (pongMessageHandler != null) { 287*1157Smax.romanov@nginx.com throw new IllegalStateException(sm.getString("wsSession.duplicateHandlerPong")); 288*1157Smax.romanov@nginx.com } 289*1157Smax.romanov@nginx.com MessageHandler handler = mhResult.getHandler(); 290*1157Smax.romanov@nginx.com if (handler instanceof MessageHandler.Whole<?>) { 291*1157Smax.romanov@nginx.com pongMessageHandler = (MessageHandler.Whole<PongMessage>) handler; 292*1157Smax.romanov@nginx.com } else { 293*1157Smax.romanov@nginx.com throw new IllegalStateException( 294*1157Smax.romanov@nginx.com sm.getString("wsSession.invalidHandlerTypePong")); 295*1157Smax.romanov@nginx.com } 296*1157Smax.romanov@nginx.com 297*1157Smax.romanov@nginx.com break; 298*1157Smax.romanov@nginx.com } 299*1157Smax.romanov@nginx.com default: { 300*1157Smax.romanov@nginx.com throw new IllegalArgumentException( 301*1157Smax.romanov@nginx.com sm.getString("wsSession.unknownHandlerType", listener, mhResult.getType())); 302*1157Smax.romanov@nginx.com } 303*1157Smax.romanov@nginx.com } 304*1157Smax.romanov@nginx.com } 305*1157Smax.romanov@nginx.com } 306*1157Smax.romanov@nginx.com 307*1157Smax.romanov@nginx.com 308*1157Smax.romanov@nginx.com @Override getMessageHandlers()309*1157Smax.romanov@nginx.com public Set<MessageHandler> getMessageHandlers() { 310*1157Smax.romanov@nginx.com checkState(); 311*1157Smax.romanov@nginx.com Set<MessageHandler> result = new HashSet<>(); 312*1157Smax.romanov@nginx.com if (binaryMessageHandler != null) { 313*1157Smax.romanov@nginx.com result.add(binaryMessageHandler); 314*1157Smax.romanov@nginx.com } 315*1157Smax.romanov@nginx.com if (textMessageHandler != null) { 316*1157Smax.romanov@nginx.com result.add(textMessageHandler); 317*1157Smax.romanov@nginx.com } 318*1157Smax.romanov@nginx.com if (pongMessageHandler != null) { 319*1157Smax.romanov@nginx.com result.add(pongMessageHandler); 320*1157Smax.romanov@nginx.com } 321*1157Smax.romanov@nginx.com return result; 322*1157Smax.romanov@nginx.com } 323*1157Smax.romanov@nginx.com 324*1157Smax.romanov@nginx.com 325*1157Smax.romanov@nginx.com @Override removeMessageHandler(MessageHandler listener)326*1157Smax.romanov@nginx.com public void removeMessageHandler(MessageHandler listener) { 327*1157Smax.romanov@nginx.com checkState(); 328*1157Smax.romanov@nginx.com if (listener == null) { 329*1157Smax.romanov@nginx.com return; 330*1157Smax.romanov@nginx.com } 331*1157Smax.romanov@nginx.com 332*1157Smax.romanov@nginx.com MessageHandler wrapped = null; 333*1157Smax.romanov@nginx.com 334*1157Smax.romanov@nginx.com if (listener instanceof WrappedMessageHandler) { 335*1157Smax.romanov@nginx.com wrapped = ((WrappedMessageHandler) listener).getWrappedHandler(); 336*1157Smax.romanov@nginx.com } 337*1157Smax.romanov@nginx.com 338*1157Smax.romanov@nginx.com if (wrapped == null) { 339*1157Smax.romanov@nginx.com wrapped = listener; 340*1157Smax.romanov@nginx.com } 341*1157Smax.romanov@nginx.com 342*1157Smax.romanov@nginx.com boolean removed = false; 343*1157Smax.romanov@nginx.com if (wrapped.equals(textMessageHandler) || listener.equals(textMessageHandler)) { 344*1157Smax.romanov@nginx.com textMessageHandler = null; 345*1157Smax.romanov@nginx.com removed = true; 346*1157Smax.romanov@nginx.com } 347*1157Smax.romanov@nginx.com 348*1157Smax.romanov@nginx.com if (wrapped.equals(binaryMessageHandler) || listener.equals(binaryMessageHandler)) { 349*1157Smax.romanov@nginx.com binaryMessageHandler = null; 350*1157Smax.romanov@nginx.com removed = true; 351*1157Smax.romanov@nginx.com } 352*1157Smax.romanov@nginx.com 353*1157Smax.romanov@nginx.com if (wrapped.equals(pongMessageHandler) || listener.equals(pongMessageHandler)) { 354*1157Smax.romanov@nginx.com pongMessageHandler = null; 355*1157Smax.romanov@nginx.com removed = true; 356*1157Smax.romanov@nginx.com } 357*1157Smax.romanov@nginx.com 358*1157Smax.romanov@nginx.com if (!removed) { 359*1157Smax.romanov@nginx.com // ISE for now. Could swallow this silently / log this if the ISE 360*1157Smax.romanov@nginx.com // becomes a problem 361*1157Smax.romanov@nginx.com throw new IllegalStateException( 362*1157Smax.romanov@nginx.com sm.getString("wsSession.removeHandlerFailed", listener)); 363*1157Smax.romanov@nginx.com } 364*1157Smax.romanov@nginx.com } 365*1157Smax.romanov@nginx.com 366*1157Smax.romanov@nginx.com 367*1157Smax.romanov@nginx.com @Override getProtocolVersion()368*1157Smax.romanov@nginx.com public String getProtocolVersion() { 369*1157Smax.romanov@nginx.com checkState(); 370*1157Smax.romanov@nginx.com return Constants.WS_VERSION_HEADER_VALUE; 371*1157Smax.romanov@nginx.com } 372*1157Smax.romanov@nginx.com 373*1157Smax.romanov@nginx.com 374*1157Smax.romanov@nginx.com @Override getNegotiatedSubprotocol()375*1157Smax.romanov@nginx.com public String getNegotiatedSubprotocol() { 376*1157Smax.romanov@nginx.com checkState(); 377*1157Smax.romanov@nginx.com return subProtocol; 378*1157Smax.romanov@nginx.com } 379*1157Smax.romanov@nginx.com 380*1157Smax.romanov@nginx.com 381*1157Smax.romanov@nginx.com @Override getNegotiatedExtensions()382*1157Smax.romanov@nginx.com public List<Extension> getNegotiatedExtensions() { 383*1157Smax.romanov@nginx.com checkState(); 384*1157Smax.romanov@nginx.com return negotiatedExtensions; 385*1157Smax.romanov@nginx.com } 386*1157Smax.romanov@nginx.com 387*1157Smax.romanov@nginx.com 388*1157Smax.romanov@nginx.com @Override isSecure()389*1157Smax.romanov@nginx.com public boolean isSecure() { 390*1157Smax.romanov@nginx.com checkState(); 391*1157Smax.romanov@nginx.com return secure; 392*1157Smax.romanov@nginx.com } 393*1157Smax.romanov@nginx.com 394*1157Smax.romanov@nginx.com 395*1157Smax.romanov@nginx.com @Override isOpen()396*1157Smax.romanov@nginx.com public boolean isOpen() { 397*1157Smax.romanov@nginx.com return state == State.OPEN; 398*1157Smax.romanov@nginx.com } 399*1157Smax.romanov@nginx.com 400*1157Smax.romanov@nginx.com 401*1157Smax.romanov@nginx.com @Override getMaxIdleTimeout()402*1157Smax.romanov@nginx.com public long getMaxIdleTimeout() { 403*1157Smax.romanov@nginx.com checkState(); 404*1157Smax.romanov@nginx.com return maxIdleTimeout; 405*1157Smax.romanov@nginx.com } 406*1157Smax.romanov@nginx.com 407*1157Smax.romanov@nginx.com 408*1157Smax.romanov@nginx.com @Override setMaxIdleTimeout(long timeout)409*1157Smax.romanov@nginx.com public void setMaxIdleTimeout(long timeout) { 410*1157Smax.romanov@nginx.com checkState(); 411*1157Smax.romanov@nginx.com this.maxIdleTimeout = timeout; 412*1157Smax.romanov@nginx.com } 413*1157Smax.romanov@nginx.com 414*1157Smax.romanov@nginx.com 415*1157Smax.romanov@nginx.com @Override setMaxBinaryMessageBufferSize(int max)416*1157Smax.romanov@nginx.com public void setMaxBinaryMessageBufferSize(int max) { 417*1157Smax.romanov@nginx.com checkState(); 418*1157Smax.romanov@nginx.com this.maxBinaryMessageBufferSize = max; 419*1157Smax.romanov@nginx.com } 420*1157Smax.romanov@nginx.com 421*1157Smax.romanov@nginx.com 422*1157Smax.romanov@nginx.com @Override getMaxBinaryMessageBufferSize()423*1157Smax.romanov@nginx.com public int getMaxBinaryMessageBufferSize() { 424*1157Smax.romanov@nginx.com checkState(); 425*1157Smax.romanov@nginx.com return maxBinaryMessageBufferSize; 426*1157Smax.romanov@nginx.com } 427*1157Smax.romanov@nginx.com 428*1157Smax.romanov@nginx.com 429*1157Smax.romanov@nginx.com @Override setMaxTextMessageBufferSize(int max)430*1157Smax.romanov@nginx.com public void setMaxTextMessageBufferSize(int max) { 431*1157Smax.romanov@nginx.com checkState(); 432*1157Smax.romanov@nginx.com this.maxTextMessageBufferSize = max; 433*1157Smax.romanov@nginx.com } 434*1157Smax.romanov@nginx.com 435*1157Smax.romanov@nginx.com 436*1157Smax.romanov@nginx.com @Override getMaxTextMessageBufferSize()437*1157Smax.romanov@nginx.com public int getMaxTextMessageBufferSize() { 438*1157Smax.romanov@nginx.com checkState(); 439*1157Smax.romanov@nginx.com return maxTextMessageBufferSize; 440*1157Smax.romanov@nginx.com } 441*1157Smax.romanov@nginx.com 442*1157Smax.romanov@nginx.com 443*1157Smax.romanov@nginx.com @Override getOpenSessions()444*1157Smax.romanov@nginx.com public Set<Session> getOpenSessions() { 445*1157Smax.romanov@nginx.com checkState(); 446*1157Smax.romanov@nginx.com return webSocketContainer.getOpenSessions(localEndpoint); 447*1157Smax.romanov@nginx.com } 448*1157Smax.romanov@nginx.com 449*1157Smax.romanov@nginx.com 450*1157Smax.romanov@nginx.com @Override getAsyncRemote()451*1157Smax.romanov@nginx.com public RemoteEndpoint.Async getAsyncRemote() { 452*1157Smax.romanov@nginx.com checkState(); 453*1157Smax.romanov@nginx.com return remoteEndpointAsync; 454*1157Smax.romanov@nginx.com } 455*1157Smax.romanov@nginx.com 456*1157Smax.romanov@nginx.com 457*1157Smax.romanov@nginx.com @Override getBasicRemote()458*1157Smax.romanov@nginx.com public RemoteEndpoint.Basic getBasicRemote() { 459*1157Smax.romanov@nginx.com checkState(); 460*1157Smax.romanov@nginx.com return remoteEndpointBasic; 461*1157Smax.romanov@nginx.com } 462*1157Smax.romanov@nginx.com 463*1157Smax.romanov@nginx.com 464*1157Smax.romanov@nginx.com @Override close()465*1157Smax.romanov@nginx.com public void close() throws IOException { 466*1157Smax.romanov@nginx.com close(new CloseReason(CloseCodes.NORMAL_CLOSURE, "")); 467*1157Smax.romanov@nginx.com } 468*1157Smax.romanov@nginx.com 469*1157Smax.romanov@nginx.com 470*1157Smax.romanov@nginx.com @Override close(CloseReason closeReason)471*1157Smax.romanov@nginx.com public void close(CloseReason closeReason) throws IOException { 472*1157Smax.romanov@nginx.com doClose(closeReason, closeReason); 473*1157Smax.romanov@nginx.com } 474*1157Smax.romanov@nginx.com 475*1157Smax.romanov@nginx.com 476*1157Smax.romanov@nginx.com /** 477*1157Smax.romanov@nginx.com * WebSocket 1.0. Section 2.1.5. 478*1157Smax.romanov@nginx.com * Need internal close method as spec requires that the local endpoint 479*1157Smax.romanov@nginx.com * receives a 1006 on timeout. 480*1157Smax.romanov@nginx.com * 481*1157Smax.romanov@nginx.com * @param closeReasonMessage The close reason to pass to the remote endpoint 482*1157Smax.romanov@nginx.com * @param closeReasonLocal The close reason to pass to the local endpoint 483*1157Smax.romanov@nginx.com */ doClose(CloseReason closeReasonMessage, CloseReason closeReasonLocal)484*1157Smax.romanov@nginx.com public void doClose(CloseReason closeReasonMessage, CloseReason closeReasonLocal) { 485*1157Smax.romanov@nginx.com // Double-checked locking. OK because state is volatile 486*1157Smax.romanov@nginx.com if (state != State.OPEN) { 487*1157Smax.romanov@nginx.com return; 488*1157Smax.romanov@nginx.com } 489*1157Smax.romanov@nginx.com 490*1157Smax.romanov@nginx.com synchronized (stateLock) { 491*1157Smax.romanov@nginx.com if (state != State.OPEN) { 492*1157Smax.romanov@nginx.com return; 493*1157Smax.romanov@nginx.com } 494*1157Smax.romanov@nginx.com 495*1157Smax.romanov@nginx.com if (log.isDebugEnabled()) { 496*1157Smax.romanov@nginx.com log.debug(sm.getString("wsSession.doClose", id)); 497*1157Smax.romanov@nginx.com } 498*1157Smax.romanov@nginx.com try { 499*1157Smax.romanov@nginx.com wsRemoteEndpoint.setBatchingAllowed(false); 500*1157Smax.romanov@nginx.com } catch (IOException e) { 501*1157Smax.romanov@nginx.com log.warn(sm.getString("wsSession.flushFailOnClose"), e); 502*1157Smax.romanov@nginx.com fireEndpointOnError(e); 503*1157Smax.romanov@nginx.com } 504*1157Smax.romanov@nginx.com 505*1157Smax.romanov@nginx.com state = State.OUTPUT_CLOSED; 506*1157Smax.romanov@nginx.com 507*1157Smax.romanov@nginx.com sendCloseMessage(closeReasonMessage); 508*1157Smax.romanov@nginx.com fireEndpointOnClose(closeReasonLocal); 509*1157Smax.romanov@nginx.com } 510*1157Smax.romanov@nginx.com 511*1157Smax.romanov@nginx.com IOException ioe = new IOException(sm.getString("wsSession.messageFailed")); 512*1157Smax.romanov@nginx.com SendResult sr = new SendResult(ioe); 513*1157Smax.romanov@nginx.com for (FutureToSendHandler f2sh : futures.keySet()) { 514*1157Smax.romanov@nginx.com f2sh.onResult(sr); 515*1157Smax.romanov@nginx.com } 516*1157Smax.romanov@nginx.com } 517*1157Smax.romanov@nginx.com 518*1157Smax.romanov@nginx.com 519*1157Smax.romanov@nginx.com /** 520*1157Smax.romanov@nginx.com * Called when a close message is received. Should only ever happen once. 521*1157Smax.romanov@nginx.com * Also called after a protocol error when the ProtocolHandler needs to 522*1157Smax.romanov@nginx.com * force the closing of the connection. 523*1157Smax.romanov@nginx.com * 524*1157Smax.romanov@nginx.com * @param closeReason The reason contained within the received close 525*1157Smax.romanov@nginx.com * message. 526*1157Smax.romanov@nginx.com */ onClose(CloseReason closeReason)527*1157Smax.romanov@nginx.com public void onClose(CloseReason closeReason) { 528*1157Smax.romanov@nginx.com 529*1157Smax.romanov@nginx.com synchronized (stateLock) { 530*1157Smax.romanov@nginx.com if (state != State.CLOSED) { 531*1157Smax.romanov@nginx.com try { 532*1157Smax.romanov@nginx.com wsRemoteEndpoint.setBatchingAllowed(false); 533*1157Smax.romanov@nginx.com } catch (IOException e) { 534*1157Smax.romanov@nginx.com log.warn(sm.getString("wsSession.flushFailOnClose"), e); 535*1157Smax.romanov@nginx.com fireEndpointOnError(e); 536*1157Smax.romanov@nginx.com } 537*1157Smax.romanov@nginx.com if (state == State.OPEN) { 538*1157Smax.romanov@nginx.com state = State.OUTPUT_CLOSED; 539*1157Smax.romanov@nginx.com sendCloseMessage(closeReason); 540*1157Smax.romanov@nginx.com fireEndpointOnClose(closeReason); 541*1157Smax.romanov@nginx.com } 542*1157Smax.romanov@nginx.com state = State.CLOSED; 543*1157Smax.romanov@nginx.com 544*1157Smax.romanov@nginx.com // Close the socket 545*1157Smax.romanov@nginx.com wsRemoteEndpoint.close(); 546*1157Smax.romanov@nginx.com } 547*1157Smax.romanov@nginx.com } 548*1157Smax.romanov@nginx.com } 549*1157Smax.romanov@nginx.com 550*1157Smax.romanov@nginx.com onClose()551*1157Smax.romanov@nginx.com public void onClose() { 552*1157Smax.romanov@nginx.com 553*1157Smax.romanov@nginx.com synchronized (stateLock) { 554*1157Smax.romanov@nginx.com if (state != State.CLOSED) { 555*1157Smax.romanov@nginx.com try { 556*1157Smax.romanov@nginx.com wsRemoteEndpoint.setBatchingAllowed(false); 557*1157Smax.romanov@nginx.com } catch (IOException e) { 558*1157Smax.romanov@nginx.com log.warn(sm.getString("wsSession.flushFailOnClose"), e); 559*1157Smax.romanov@nginx.com fireEndpointOnError(e); 560*1157Smax.romanov@nginx.com } 561*1157Smax.romanov@nginx.com if (state == State.OPEN) { 562*1157Smax.romanov@nginx.com state = State.OUTPUT_CLOSED; 563*1157Smax.romanov@nginx.com fireEndpointOnClose(new CloseReason( 564*1157Smax.romanov@nginx.com CloseReason.CloseCodes.NORMAL_CLOSURE, "")); 565*1157Smax.romanov@nginx.com } 566*1157Smax.romanov@nginx.com state = State.CLOSED; 567*1157Smax.romanov@nginx.com 568*1157Smax.romanov@nginx.com // Close the socket 569*1157Smax.romanov@nginx.com wsRemoteEndpoint.close(); 570*1157Smax.romanov@nginx.com } 571*1157Smax.romanov@nginx.com } 572*1157Smax.romanov@nginx.com } 573*1157Smax.romanov@nginx.com 574*1157Smax.romanov@nginx.com fireEndpointOnClose(CloseReason closeReason)575*1157Smax.romanov@nginx.com private void fireEndpointOnClose(CloseReason closeReason) { 576*1157Smax.romanov@nginx.com 577*1157Smax.romanov@nginx.com // Fire the onClose event 578*1157Smax.romanov@nginx.com Throwable throwable = null; 579*1157Smax.romanov@nginx.com InstanceManager instanceManager = webSocketContainer.getInstanceManager(); 580*1157Smax.romanov@nginx.com if (instanceManager == null) { 581*1157Smax.romanov@nginx.com instanceManager = InstanceManagerBindings.get(applicationClassLoader); 582*1157Smax.romanov@nginx.com } 583*1157Smax.romanov@nginx.com Thread t = Thread.currentThread(); 584*1157Smax.romanov@nginx.com ClassLoader cl = t.getContextClassLoader(); 585*1157Smax.romanov@nginx.com t.setContextClassLoader(applicationClassLoader); 586*1157Smax.romanov@nginx.com try { 587*1157Smax.romanov@nginx.com localEndpoint.onClose(this, closeReason); 588*1157Smax.romanov@nginx.com } catch (Throwable t1) { 589*1157Smax.romanov@nginx.com ExceptionUtils.handleThrowable(t1); 590*1157Smax.romanov@nginx.com throwable = t1; 591*1157Smax.romanov@nginx.com } finally { 592*1157Smax.romanov@nginx.com if (instanceManager != null) { 593*1157Smax.romanov@nginx.com try { 594*1157Smax.romanov@nginx.com instanceManager.destroyInstance(localEndpoint); 595*1157Smax.romanov@nginx.com } catch (Throwable t2) { 596*1157Smax.romanov@nginx.com ExceptionUtils.handleThrowable(t2); 597*1157Smax.romanov@nginx.com if (throwable == null) { 598*1157Smax.romanov@nginx.com throwable = t2; 599*1157Smax.romanov@nginx.com } 600*1157Smax.romanov@nginx.com } 601*1157Smax.romanov@nginx.com } 602*1157Smax.romanov@nginx.com t.setContextClassLoader(cl); 603*1157Smax.romanov@nginx.com } 604*1157Smax.romanov@nginx.com 605*1157Smax.romanov@nginx.com if (throwable != null) { 606*1157Smax.romanov@nginx.com fireEndpointOnError(throwable); 607*1157Smax.romanov@nginx.com } 608*1157Smax.romanov@nginx.com } 609*1157Smax.romanov@nginx.com 610*1157Smax.romanov@nginx.com fireEndpointOnError(Throwable throwable)611*1157Smax.romanov@nginx.com private void fireEndpointOnError(Throwable throwable) { 612*1157Smax.romanov@nginx.com 613*1157Smax.romanov@nginx.com // Fire the onError event 614*1157Smax.romanov@nginx.com Thread t = Thread.currentThread(); 615*1157Smax.romanov@nginx.com ClassLoader cl = t.getContextClassLoader(); 616*1157Smax.romanov@nginx.com t.setContextClassLoader(applicationClassLoader); 617*1157Smax.romanov@nginx.com try { 618*1157Smax.romanov@nginx.com localEndpoint.onError(this, throwable); 619*1157Smax.romanov@nginx.com } finally { 620*1157Smax.romanov@nginx.com t.setContextClassLoader(cl); 621*1157Smax.romanov@nginx.com } 622*1157Smax.romanov@nginx.com } 623*1157Smax.romanov@nginx.com 624*1157Smax.romanov@nginx.com sendCloseMessage(CloseReason closeReason)625*1157Smax.romanov@nginx.com private void sendCloseMessage(CloseReason closeReason) { 626*1157Smax.romanov@nginx.com // 125 is maximum size for the payload of a control message 627*1157Smax.romanov@nginx.com ByteBuffer msg = ByteBuffer.allocate(125); 628*1157Smax.romanov@nginx.com CloseCode closeCode = closeReason.getCloseCode(); 629*1157Smax.romanov@nginx.com // CLOSED_ABNORMALLY should not be put on the wire 630*1157Smax.romanov@nginx.com if (closeCode == CloseCodes.CLOSED_ABNORMALLY) { 631*1157Smax.romanov@nginx.com // PROTOCOL_ERROR is probably better than GOING_AWAY here 632*1157Smax.romanov@nginx.com msg.putShort((short) CloseCodes.PROTOCOL_ERROR.getCode()); 633*1157Smax.romanov@nginx.com } else { 634*1157Smax.romanov@nginx.com msg.putShort((short) closeCode.getCode()); 635*1157Smax.romanov@nginx.com } 636*1157Smax.romanov@nginx.com 637*1157Smax.romanov@nginx.com String reason = closeReason.getReasonPhrase(); 638*1157Smax.romanov@nginx.com if (reason != null && reason.length() > 0) { 639*1157Smax.romanov@nginx.com appendCloseReasonWithTruncation(msg, reason); 640*1157Smax.romanov@nginx.com } 641*1157Smax.romanov@nginx.com msg.flip(); 642*1157Smax.romanov@nginx.com try { 643*1157Smax.romanov@nginx.com wsRemoteEndpoint.sendMessageBlock(Constants.OPCODE_CLOSE, msg, true); 644*1157Smax.romanov@nginx.com } catch (IOException | WritePendingException e) { 645*1157Smax.romanov@nginx.com // Failed to send close message. Close the socket and let the caller 646*1157Smax.romanov@nginx.com // deal with the Exception 647*1157Smax.romanov@nginx.com if (log.isDebugEnabled()) { 648*1157Smax.romanov@nginx.com log.debug(sm.getString("wsSession.sendCloseFail", id), e); 649*1157Smax.romanov@nginx.com } 650*1157Smax.romanov@nginx.com wsRemoteEndpoint.close(); 651*1157Smax.romanov@nginx.com // Failure to send a close message is not unexpected in the case of 652*1157Smax.romanov@nginx.com // an abnormal closure (usually triggered by a failure to read/write 653*1157Smax.romanov@nginx.com // from/to the client. In this case do not trigger the endpoint's 654*1157Smax.romanov@nginx.com // error handling 655*1157Smax.romanov@nginx.com if (closeCode != CloseCodes.CLOSED_ABNORMALLY) { 656*1157Smax.romanov@nginx.com localEndpoint.onError(this, e); 657*1157Smax.romanov@nginx.com } 658*1157Smax.romanov@nginx.com } finally { 659*1157Smax.romanov@nginx.com webSocketContainer.unregisterSession(localEndpoint, this); 660*1157Smax.romanov@nginx.com } 661*1157Smax.romanov@nginx.com } 662*1157Smax.romanov@nginx.com 663*1157Smax.romanov@nginx.com 664*1157Smax.romanov@nginx.com /** 665*1157Smax.romanov@nginx.com * Use protected so unit tests can access this method directly. 666*1157Smax.romanov@nginx.com * @param msg The message 667*1157Smax.romanov@nginx.com * @param reason The reason 668*1157Smax.romanov@nginx.com */ appendCloseReasonWithTruncation(ByteBuffer msg, String reason)669*1157Smax.romanov@nginx.com protected static void appendCloseReasonWithTruncation(ByteBuffer msg, String reason) { 670*1157Smax.romanov@nginx.com // Once the close code has been added there are a maximum of 123 bytes 671*1157Smax.romanov@nginx.com // left for the reason phrase. If it is truncated then care needs to be 672*1157Smax.romanov@nginx.com // taken to ensure the bytes are not truncated in the middle of a 673*1157Smax.romanov@nginx.com // multi-byte UTF-8 character. 674*1157Smax.romanov@nginx.com byte[] reasonBytes = reason.getBytes(StandardCharsets.UTF_8); 675*1157Smax.romanov@nginx.com 676*1157Smax.romanov@nginx.com if (reasonBytes.length <= 123) { 677*1157Smax.romanov@nginx.com // No need to truncate 678*1157Smax.romanov@nginx.com msg.put(reasonBytes); 679*1157Smax.romanov@nginx.com } else { 680*1157Smax.romanov@nginx.com // Need to truncate 681*1157Smax.romanov@nginx.com int remaining = 123 - ELLIPSIS_BYTES_LEN; 682*1157Smax.romanov@nginx.com int pos = 0; 683*1157Smax.romanov@nginx.com byte[] bytesNext = reason.substring(pos, pos + 1).getBytes(StandardCharsets.UTF_8); 684*1157Smax.romanov@nginx.com while (remaining >= bytesNext.length) { 685*1157Smax.romanov@nginx.com msg.put(bytesNext); 686*1157Smax.romanov@nginx.com remaining -= bytesNext.length; 687*1157Smax.romanov@nginx.com pos++; 688*1157Smax.romanov@nginx.com bytesNext = reason.substring(pos, pos + 1).getBytes(StandardCharsets.UTF_8); 689*1157Smax.romanov@nginx.com } 690*1157Smax.romanov@nginx.com msg.put(ELLIPSIS_BYTES); 691*1157Smax.romanov@nginx.com } 692*1157Smax.romanov@nginx.com } 693*1157Smax.romanov@nginx.com 694*1157Smax.romanov@nginx.com 695*1157Smax.romanov@nginx.com /** 696*1157Smax.romanov@nginx.com * Make the session aware of a {@link FutureToSendHandler} that will need to 697*1157Smax.romanov@nginx.com * be forcibly closed if the session closes before the 698*1157Smax.romanov@nginx.com * {@link FutureToSendHandler} completes. 699*1157Smax.romanov@nginx.com * @param f2sh The handler 700*1157Smax.romanov@nginx.com */ registerFuture(FutureToSendHandler f2sh)701*1157Smax.romanov@nginx.com protected void registerFuture(FutureToSendHandler f2sh) { 702*1157Smax.romanov@nginx.com // Ideally, this code should sync on stateLock so that the correct 703*1157Smax.romanov@nginx.com // action is taken based on the current state of the connection. 704*1157Smax.romanov@nginx.com // However, a sync on stateLock can't be used here as it will create the 705*1157Smax.romanov@nginx.com // possibility of a dead-lock. See BZ 61183. 706*1157Smax.romanov@nginx.com // Therefore, a slightly less efficient approach is used. 707*1157Smax.romanov@nginx.com 708*1157Smax.romanov@nginx.com // Always register the future. 709*1157Smax.romanov@nginx.com futures.put(f2sh, f2sh); 710*1157Smax.romanov@nginx.com 711*1157Smax.romanov@nginx.com if (state == State.OPEN) { 712*1157Smax.romanov@nginx.com // The session is open. The future has been registered with the open 713*1157Smax.romanov@nginx.com // session. Normal processing continues. 714*1157Smax.romanov@nginx.com return; 715*1157Smax.romanov@nginx.com } 716*1157Smax.romanov@nginx.com 717*1157Smax.romanov@nginx.com // The session is closed. The future may or may not have been registered 718*1157Smax.romanov@nginx.com // in time for it to be processed during session closure. 719*1157Smax.romanov@nginx.com 720*1157Smax.romanov@nginx.com if (f2sh.isDone()) { 721*1157Smax.romanov@nginx.com // The future has completed. It is not known if the future was 722*1157Smax.romanov@nginx.com // completed normally by the I/O layer or in error by doClose(). It 723*1157Smax.romanov@nginx.com // doesn't matter which. There is nothing more to do here. 724*1157Smax.romanov@nginx.com return; 725*1157Smax.romanov@nginx.com } 726*1157Smax.romanov@nginx.com 727*1157Smax.romanov@nginx.com // The session is closed. The Future had not completed when last checked. 728*1157Smax.romanov@nginx.com // There is a small timing window that means the Future may have been 729*1157Smax.romanov@nginx.com // completed since the last check. There is also the possibility that 730*1157Smax.romanov@nginx.com // the Future was not registered in time to be cleaned up during session 731*1157Smax.romanov@nginx.com // close. 732*1157Smax.romanov@nginx.com // Attempt to complete the Future with an error result as this ensures 733*1157Smax.romanov@nginx.com // that the Future completes and any client code waiting on it does not 734*1157Smax.romanov@nginx.com // hang. It is slightly inefficient since the Future may have been 735*1157Smax.romanov@nginx.com // completed in another thread or another thread may be about to 736*1157Smax.romanov@nginx.com // complete the Future but knowing if this is the case requires the sync 737*1157Smax.romanov@nginx.com // on stateLock (see above). 738*1157Smax.romanov@nginx.com // Note: If multiple attempts are made to complete the Future, the 739*1157Smax.romanov@nginx.com // second and subsequent attempts are ignored. 740*1157Smax.romanov@nginx.com 741*1157Smax.romanov@nginx.com IOException ioe = new IOException(sm.getString("wsSession.messageFailed")); 742*1157Smax.romanov@nginx.com SendResult sr = new SendResult(ioe); 743*1157Smax.romanov@nginx.com f2sh.onResult(sr); 744*1157Smax.romanov@nginx.com } 745*1157Smax.romanov@nginx.com 746*1157Smax.romanov@nginx.com 747*1157Smax.romanov@nginx.com /** 748*1157Smax.romanov@nginx.com * Remove a {@link FutureToSendHandler} from the set of tracked instances. 749*1157Smax.romanov@nginx.com * @param f2sh The handler 750*1157Smax.romanov@nginx.com */ unregisterFuture(FutureToSendHandler f2sh)751*1157Smax.romanov@nginx.com protected void unregisterFuture(FutureToSendHandler f2sh) { 752*1157Smax.romanov@nginx.com futures.remove(f2sh); 753*1157Smax.romanov@nginx.com } 754*1157Smax.romanov@nginx.com 755*1157Smax.romanov@nginx.com 756*1157Smax.romanov@nginx.com @Override getRequestURI()757*1157Smax.romanov@nginx.com public URI getRequestURI() { 758*1157Smax.romanov@nginx.com checkState(); 759*1157Smax.romanov@nginx.com return requestUri; 760*1157Smax.romanov@nginx.com } 761*1157Smax.romanov@nginx.com 762*1157Smax.romanov@nginx.com 763*1157Smax.romanov@nginx.com @Override getRequestParameterMap()764*1157Smax.romanov@nginx.com public Map<String, List<String>> getRequestParameterMap() { 765*1157Smax.romanov@nginx.com checkState(); 766*1157Smax.romanov@nginx.com return requestParameterMap; 767*1157Smax.romanov@nginx.com } 768*1157Smax.romanov@nginx.com 769*1157Smax.romanov@nginx.com 770*1157Smax.romanov@nginx.com @Override getQueryString()771*1157Smax.romanov@nginx.com public String getQueryString() { 772*1157Smax.romanov@nginx.com checkState(); 773*1157Smax.romanov@nginx.com return queryString; 774*1157Smax.romanov@nginx.com } 775*1157Smax.romanov@nginx.com 776*1157Smax.romanov@nginx.com 777*1157Smax.romanov@nginx.com @Override getUserPrincipal()778*1157Smax.romanov@nginx.com public Principal getUserPrincipal() { 779*1157Smax.romanov@nginx.com checkState(); 780*1157Smax.romanov@nginx.com return userPrincipal; 781*1157Smax.romanov@nginx.com } 782*1157Smax.romanov@nginx.com 783*1157Smax.romanov@nginx.com 784*1157Smax.romanov@nginx.com @Override getPathParameters()785*1157Smax.romanov@nginx.com public Map<String, String> getPathParameters() { 786*1157Smax.romanov@nginx.com checkState(); 787*1157Smax.romanov@nginx.com return pathParameters; 788*1157Smax.romanov@nginx.com } 789*1157Smax.romanov@nginx.com 790*1157Smax.romanov@nginx.com 791*1157Smax.romanov@nginx.com @Override getId()792*1157Smax.romanov@nginx.com public String getId() { 793*1157Smax.romanov@nginx.com return id; 794*1157Smax.romanov@nginx.com } 795*1157Smax.romanov@nginx.com 796*1157Smax.romanov@nginx.com 797*1157Smax.romanov@nginx.com @Override getUserProperties()798*1157Smax.romanov@nginx.com public Map<String, Object> getUserProperties() { 799*1157Smax.romanov@nginx.com checkState(); 800*1157Smax.romanov@nginx.com return userProperties; 801*1157Smax.romanov@nginx.com } 802*1157Smax.romanov@nginx.com 803*1157Smax.romanov@nginx.com getLocal()804*1157Smax.romanov@nginx.com public Endpoint getLocal() { 805*1157Smax.romanov@nginx.com return localEndpoint; 806*1157Smax.romanov@nginx.com } 807*1157Smax.romanov@nginx.com 808*1157Smax.romanov@nginx.com getHttpSessionId()809*1157Smax.romanov@nginx.com public String getHttpSessionId() { 810*1157Smax.romanov@nginx.com return httpSessionId; 811*1157Smax.romanov@nginx.com } 812*1157Smax.romanov@nginx.com 813*1157Smax.romanov@nginx.com private ByteBuffer rawFragments; 814*1157Smax.romanov@nginx.com processFrame(ByteBuffer buf, byte opCode, boolean last)815*1157Smax.romanov@nginx.com public void processFrame(ByteBuffer buf, byte opCode, boolean last) 816*1157Smax.romanov@nginx.com throws IOException 817*1157Smax.romanov@nginx.com { 818*1157Smax.romanov@nginx.com if (state == State.CLOSED) { 819*1157Smax.romanov@nginx.com return; 820*1157Smax.romanov@nginx.com } 821*1157Smax.romanov@nginx.com 822*1157Smax.romanov@nginx.com if (opCode == Constants.OPCODE_CONTINUATION) { 823*1157Smax.romanov@nginx.com opCode = startOpCode; 824*1157Smax.romanov@nginx.com 825*1157Smax.romanov@nginx.com if (rawFragments != null && rawFragments.position() > 0) { 826*1157Smax.romanov@nginx.com rawFragments.put(buf); 827*1157Smax.romanov@nginx.com rawFragments.flip(); 828*1157Smax.romanov@nginx.com buf = rawFragments; 829*1157Smax.romanov@nginx.com } 830*1157Smax.romanov@nginx.com } else { 831*1157Smax.romanov@nginx.com if (!last && (opCode == Constants.OPCODE_BINARY || 832*1157Smax.romanov@nginx.com opCode == Constants.OPCODE_TEXT)) { 833*1157Smax.romanov@nginx.com startOpCode = opCode; 834*1157Smax.romanov@nginx.com 835*1157Smax.romanov@nginx.com if (rawFragments != null) { 836*1157Smax.romanov@nginx.com rawFragments.clear(); 837*1157Smax.romanov@nginx.com } 838*1157Smax.romanov@nginx.com } 839*1157Smax.romanov@nginx.com } 840*1157Smax.romanov@nginx.com 841*1157Smax.romanov@nginx.com if (last) { 842*1157Smax.romanov@nginx.com startOpCode = Constants.OPCODE_CONTINUATION; 843*1157Smax.romanov@nginx.com } 844*1157Smax.romanov@nginx.com 845*1157Smax.romanov@nginx.com if (opCode == Constants.OPCODE_PONG) { 846*1157Smax.romanov@nginx.com if (pongMessageHandler != null) { 847*1157Smax.romanov@nginx.com final ByteBuffer b = buf; 848*1157Smax.romanov@nginx.com 849*1157Smax.romanov@nginx.com PongMessage pongMessage = new PongMessage() { 850*1157Smax.romanov@nginx.com @Override 851*1157Smax.romanov@nginx.com public ByteBuffer getApplicationData() { 852*1157Smax.romanov@nginx.com return b; 853*1157Smax.romanov@nginx.com } 854*1157Smax.romanov@nginx.com }; 855*1157Smax.romanov@nginx.com 856*1157Smax.romanov@nginx.com pongMessageHandler.onMessage(pongMessage); 857*1157Smax.romanov@nginx.com } 858*1157Smax.romanov@nginx.com } 859*1157Smax.romanov@nginx.com 860*1157Smax.romanov@nginx.com if (opCode == Constants.OPCODE_CLOSE) { 861*1157Smax.romanov@nginx.com CloseReason closeReason; 862*1157Smax.romanov@nginx.com 863*1157Smax.romanov@nginx.com if (buf.remaining() >= 2) { 864*1157Smax.romanov@nginx.com short closeCode = buf.order(ByteOrder.BIG_ENDIAN).getShort(); 865*1157Smax.romanov@nginx.com 866*1157Smax.romanov@nginx.com closeReason = new CloseReason( 867*1157Smax.romanov@nginx.com CloseReason.CloseCodes.getCloseCode(closeCode), 868*1157Smax.romanov@nginx.com buf.asCharBuffer().toString()); 869*1157Smax.romanov@nginx.com } else { 870*1157Smax.romanov@nginx.com closeReason = new CloseReason( 871*1157Smax.romanov@nginx.com CloseReason.CloseCodes.NORMAL_CLOSURE, ""); 872*1157Smax.romanov@nginx.com } 873*1157Smax.romanov@nginx.com 874*1157Smax.romanov@nginx.com onClose(closeReason); 875*1157Smax.romanov@nginx.com } 876*1157Smax.romanov@nginx.com 877*1157Smax.romanov@nginx.com if (opCode == Constants.OPCODE_BINARY) { 878*1157Smax.romanov@nginx.com onMessage(buf, last); 879*1157Smax.romanov@nginx.com } 880*1157Smax.romanov@nginx.com 881*1157Smax.romanov@nginx.com if (opCode == Constants.OPCODE_TEXT) { 882*1157Smax.romanov@nginx.com if (messageBufferText.position() == 0 && maxTextMessageBufferSize != messageBufferText.capacity()) { 883*1157Smax.romanov@nginx.com messageBufferText = CharBuffer.allocate(maxTextMessageBufferSize); 884*1157Smax.romanov@nginx.com } 885*1157Smax.romanov@nginx.com 886*1157Smax.romanov@nginx.com CoderResult cr = utf8DecoderMessage.decode(buf, messageBufferText, last); 887*1157Smax.romanov@nginx.com if (cr.isError()) { 888*1157Smax.romanov@nginx.com throw new WsIOException(new CloseReason( 889*1157Smax.romanov@nginx.com CloseCodes.NOT_CONSISTENT, 890*1157Smax.romanov@nginx.com sm.getString("wsFrame.invalidUtf8"))); 891*1157Smax.romanov@nginx.com } else if (cr.isOverflow()) { 892*1157Smax.romanov@nginx.com // Ran out of space in text buffer - flush it 893*1157Smax.romanov@nginx.com if (hasTextPartial()) { 894*1157Smax.romanov@nginx.com do { 895*1157Smax.romanov@nginx.com onMessage(messageBufferText, false); 896*1157Smax.romanov@nginx.com 897*1157Smax.romanov@nginx.com cr = utf8DecoderMessage.decode(buf, messageBufferText, last); 898*1157Smax.romanov@nginx.com } while (cr.isOverflow()); 899*1157Smax.romanov@nginx.com } else { 900*1157Smax.romanov@nginx.com throw new WsIOException(new CloseReason( 901*1157Smax.romanov@nginx.com CloseCodes.TOO_BIG, 902*1157Smax.romanov@nginx.com sm.getString("wsFrame.textMessageTooBig"))); 903*1157Smax.romanov@nginx.com } 904*1157Smax.romanov@nginx.com } else if (cr.isUnderflow() && !last) { 905*1157Smax.romanov@nginx.com updateRawFragments(buf, last); 906*1157Smax.romanov@nginx.com 907*1157Smax.romanov@nginx.com if (hasTextPartial()) { 908*1157Smax.romanov@nginx.com onMessage(messageBufferText, false); 909*1157Smax.romanov@nginx.com } 910*1157Smax.romanov@nginx.com 911*1157Smax.romanov@nginx.com return; 912*1157Smax.romanov@nginx.com } 913*1157Smax.romanov@nginx.com 914*1157Smax.romanov@nginx.com if (last) { 915*1157Smax.romanov@nginx.com utf8DecoderMessage.reset(); 916*1157Smax.romanov@nginx.com } 917*1157Smax.romanov@nginx.com 918*1157Smax.romanov@nginx.com updateRawFragments(buf, last); 919*1157Smax.romanov@nginx.com 920*1157Smax.romanov@nginx.com onMessage(messageBufferText, last); 921*1157Smax.romanov@nginx.com } 922*1157Smax.romanov@nginx.com } 923*1157Smax.romanov@nginx.com 924*1157Smax.romanov@nginx.com hasTextPartial()925*1157Smax.romanov@nginx.com private boolean hasTextPartial() { 926*1157Smax.romanov@nginx.com return textMessageHandler instanceof MessageHandler.Partial<?>; 927*1157Smax.romanov@nginx.com } 928*1157Smax.romanov@nginx.com 929*1157Smax.romanov@nginx.com onMessage(CharBuffer buf, boolean last)930*1157Smax.romanov@nginx.com private void onMessage(CharBuffer buf, boolean last) throws IOException { 931*1157Smax.romanov@nginx.com buf.flip(); 932*1157Smax.romanov@nginx.com try { 933*1157Smax.romanov@nginx.com onMessage(buf.toString(), last); 934*1157Smax.romanov@nginx.com } catch (Throwable t) { 935*1157Smax.romanov@nginx.com handleThrowableOnSend(t); 936*1157Smax.romanov@nginx.com } finally { 937*1157Smax.romanov@nginx.com buf.clear(); 938*1157Smax.romanov@nginx.com } 939*1157Smax.romanov@nginx.com } 940*1157Smax.romanov@nginx.com 941*1157Smax.romanov@nginx.com updateRawFragments(ByteBuffer buf, boolean last)942*1157Smax.romanov@nginx.com private void updateRawFragments(ByteBuffer buf, boolean last) { 943*1157Smax.romanov@nginx.com if (!last && buf.remaining() > 0) { 944*1157Smax.romanov@nginx.com if (buf == rawFragments) { 945*1157Smax.romanov@nginx.com buf.compact(); 946*1157Smax.romanov@nginx.com } else { 947*1157Smax.romanov@nginx.com if (rawFragments == null || (rawFragments.position() == 0 && maxTextMessageBufferSize != rawFragments.capacity())) { 948*1157Smax.romanov@nginx.com rawFragments = ByteBuffer.allocateDirect(maxTextMessageBufferSize); 949*1157Smax.romanov@nginx.com } 950*1157Smax.romanov@nginx.com rawFragments.put(buf); 951*1157Smax.romanov@nginx.com } 952*1157Smax.romanov@nginx.com } else { 953*1157Smax.romanov@nginx.com if (rawFragments != null) { 954*1157Smax.romanov@nginx.com rawFragments.clear(); 955*1157Smax.romanov@nginx.com } 956*1157Smax.romanov@nginx.com } 957*1157Smax.romanov@nginx.com } 958*1157Smax.romanov@nginx.com 959*1157Smax.romanov@nginx.com 960*1157Smax.romanov@nginx.com @SuppressWarnings("unchecked") onMessage(String text, boolean last)961*1157Smax.romanov@nginx.com public void onMessage(String text, boolean last) { 962*1157Smax.romanov@nginx.com if (hasTextPartial()) { 963*1157Smax.romanov@nginx.com ((MessageHandler.Partial<String>) textMessageHandler).onMessage(text, last); 964*1157Smax.romanov@nginx.com } else { 965*1157Smax.romanov@nginx.com // Caller ensures last == true if this branch is used 966*1157Smax.romanov@nginx.com ((MessageHandler.Whole<String>) textMessageHandler).onMessage(text); 967*1157Smax.romanov@nginx.com } 968*1157Smax.romanov@nginx.com } 969*1157Smax.romanov@nginx.com 970*1157Smax.romanov@nginx.com 971*1157Smax.romanov@nginx.com @SuppressWarnings("unchecked") onMessage(ByteBuffer buf, boolean last)972*1157Smax.romanov@nginx.com public void onMessage(ByteBuffer buf, boolean last) 973*1157Smax.romanov@nginx.com throws IOException 974*1157Smax.romanov@nginx.com { 975*1157Smax.romanov@nginx.com if (binaryMessageHandler instanceof MessageHandler.Partial<?>) { 976*1157Smax.romanov@nginx.com ((MessageHandler.Partial<ByteBuffer>) binaryMessageHandler).onMessage(buf, last); 977*1157Smax.romanov@nginx.com } else { 978*1157Smax.romanov@nginx.com if (last && (binaryBuffer == null || binaryBuffer.position() == 0)) { 979*1157Smax.romanov@nginx.com ((MessageHandler.Whole<ByteBuffer>) binaryMessageHandler).onMessage(buf); 980*1157Smax.romanov@nginx.com return; 981*1157Smax.romanov@nginx.com } 982*1157Smax.romanov@nginx.com 983*1157Smax.romanov@nginx.com if (binaryBuffer == null || 984*1157Smax.romanov@nginx.com (binaryBuffer.position() == 0 && binaryBuffer.capacity() != maxBinaryMessageBufferSize)) 985*1157Smax.romanov@nginx.com { 986*1157Smax.romanov@nginx.com binaryBuffer = ByteBuffer.allocateDirect(maxBinaryMessageBufferSize); 987*1157Smax.romanov@nginx.com } 988*1157Smax.romanov@nginx.com 989*1157Smax.romanov@nginx.com if (binaryBuffer.remaining() < buf.remaining()) { 990*1157Smax.romanov@nginx.com throw new WsIOException(new CloseReason( 991*1157Smax.romanov@nginx.com CloseCodes.TOO_BIG, 992*1157Smax.romanov@nginx.com sm.getString("wsFrame.textMessageTooBig"))); 993*1157Smax.romanov@nginx.com } 994*1157Smax.romanov@nginx.com 995*1157Smax.romanov@nginx.com binaryBuffer.put(buf); 996*1157Smax.romanov@nginx.com 997*1157Smax.romanov@nginx.com if (last) { 998*1157Smax.romanov@nginx.com binaryBuffer.flip(); 999*1157Smax.romanov@nginx.com try { 1000*1157Smax.romanov@nginx.com ((MessageHandler.Whole<ByteBuffer>) binaryMessageHandler).onMessage(binaryBuffer); 1001*1157Smax.romanov@nginx.com } finally { 1002*1157Smax.romanov@nginx.com binaryBuffer.clear(); 1003*1157Smax.romanov@nginx.com } 1004*1157Smax.romanov@nginx.com } 1005*1157Smax.romanov@nginx.com } 1006*1157Smax.romanov@nginx.com } 1007*1157Smax.romanov@nginx.com 1008*1157Smax.romanov@nginx.com handleThrowableOnSend(Throwable t)1009*1157Smax.romanov@nginx.com private void handleThrowableOnSend(Throwable t) throws WsIOException { 1010*1157Smax.romanov@nginx.com ExceptionUtils.handleThrowable(t); 1011*1157Smax.romanov@nginx.com getLocal().onError(this, t); 1012*1157Smax.romanov@nginx.com CloseReason cr = new CloseReason(CloseCodes.CLOSED_ABNORMALLY, 1013*1157Smax.romanov@nginx.com sm.getString("wsFrame.ioeTriggeredClose")); 1014*1157Smax.romanov@nginx.com throw new WsIOException(cr); 1015*1157Smax.romanov@nginx.com } 1016*1157Smax.romanov@nginx.com 1017*1157Smax.romanov@nginx.com getTextMessageHandler()1018*1157Smax.romanov@nginx.com protected MessageHandler getTextMessageHandler() { 1019*1157Smax.romanov@nginx.com return textMessageHandler; 1020*1157Smax.romanov@nginx.com } 1021*1157Smax.romanov@nginx.com 1022*1157Smax.romanov@nginx.com getBinaryMessageHandler()1023*1157Smax.romanov@nginx.com protected MessageHandler getBinaryMessageHandler() { 1024*1157Smax.romanov@nginx.com return binaryMessageHandler; 1025*1157Smax.romanov@nginx.com } 1026*1157Smax.romanov@nginx.com 1027*1157Smax.romanov@nginx.com getPongMessageHandler()1028*1157Smax.romanov@nginx.com protected MessageHandler.Whole<PongMessage> getPongMessageHandler() { 1029*1157Smax.romanov@nginx.com return pongMessageHandler; 1030*1157Smax.romanov@nginx.com } 1031*1157Smax.romanov@nginx.com 1032*1157Smax.romanov@nginx.com updateLastActive()1033*1157Smax.romanov@nginx.com protected void updateLastActive() { 1034*1157Smax.romanov@nginx.com lastActive = System.currentTimeMillis(); 1035*1157Smax.romanov@nginx.com } 1036*1157Smax.romanov@nginx.com 1037*1157Smax.romanov@nginx.com checkExpiration()1038*1157Smax.romanov@nginx.com protected void checkExpiration() { 1039*1157Smax.romanov@nginx.com long timeout = maxIdleTimeout; 1040*1157Smax.romanov@nginx.com if (timeout < 1) { 1041*1157Smax.romanov@nginx.com return; 1042*1157Smax.romanov@nginx.com } 1043*1157Smax.romanov@nginx.com 1044*1157Smax.romanov@nginx.com if (System.currentTimeMillis() - lastActive > timeout) { 1045*1157Smax.romanov@nginx.com String msg = sm.getString("wsSession.timeout", getId()); 1046*1157Smax.romanov@nginx.com if (log.isDebugEnabled()) { 1047*1157Smax.romanov@nginx.com log.debug(msg); 1048*1157Smax.romanov@nginx.com } 1049*1157Smax.romanov@nginx.com doClose(new CloseReason(CloseCodes.GOING_AWAY, msg), 1050*1157Smax.romanov@nginx.com new CloseReason(CloseCodes.CLOSED_ABNORMALLY, msg)); 1051*1157Smax.romanov@nginx.com } 1052*1157Smax.romanov@nginx.com } 1053*1157Smax.romanov@nginx.com 1054*1157Smax.romanov@nginx.com checkState()1055*1157Smax.romanov@nginx.com private void checkState() { 1056*1157Smax.romanov@nginx.com if (state == State.CLOSED) { 1057*1157Smax.romanov@nginx.com /* 1058*1157Smax.romanov@nginx.com * As per RFC 6455, a WebSocket connection is considered to be 1059*1157Smax.romanov@nginx.com * closed once a peer has sent and received a WebSocket close frame. 1060*1157Smax.romanov@nginx.com */ 1061*1157Smax.romanov@nginx.com throw new IllegalStateException(sm.getString("wsSession.closed", id)); 1062*1157Smax.romanov@nginx.com } 1063*1157Smax.romanov@nginx.com } 1064*1157Smax.romanov@nginx.com 1065*1157Smax.romanov@nginx.com private enum State { 1066*1157Smax.romanov@nginx.com OPEN, 1067*1157Smax.romanov@nginx.com OUTPUT_CLOSED, 1068*1157Smax.romanov@nginx.com CLOSED 1069*1157Smax.romanov@nginx.com } 1070*1157Smax.romanov@nginx.com } 1071