xref: /unit/src/java/nginx/unit/websocket/WsSession.java (revision 1157:7ae152bda303)
1*1157Smax.romanov@nginx.com /*
2*1157Smax.romanov@nginx.com  * Licensed to the Apache Software Foundation (ASF) under one or more
3*1157Smax.romanov@nginx.com  * contributor license agreements.  See the NOTICE file distributed with
4*1157Smax.romanov@nginx.com  * this work for additional information regarding copyright ownership.
5*1157Smax.romanov@nginx.com  * The ASF licenses this file to You under the Apache License, Version 2.0
6*1157Smax.romanov@nginx.com  * (the "License"); you may not use this file except in compliance with
7*1157Smax.romanov@nginx.com  * the License.  You may obtain a copy of the License at
8*1157Smax.romanov@nginx.com  *
9*1157Smax.romanov@nginx.com  *     http://www.apache.org/licenses/LICENSE-2.0
10*1157Smax.romanov@nginx.com  *
11*1157Smax.romanov@nginx.com  * Unless required by applicable law or agreed to in writing, software
12*1157Smax.romanov@nginx.com  * distributed under the License is distributed on an "AS IS" BASIS,
13*1157Smax.romanov@nginx.com  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*1157Smax.romanov@nginx.com  * See the License for the specific language governing permissions and
15*1157Smax.romanov@nginx.com  * limitations under the License.
16*1157Smax.romanov@nginx.com  */
17*1157Smax.romanov@nginx.com package nginx.unit.websocket;
18*1157Smax.romanov@nginx.com 
19*1157Smax.romanov@nginx.com import java.io.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