View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.filter.keepalive;
21  
22  import org.apache.mina.core.filterchain.IoFilter;
23  import org.apache.mina.core.filterchain.IoFilterAdapter;
24  import org.apache.mina.core.filterchain.IoFilterChain;
25  import org.apache.mina.core.service.IoHandler;
26  import org.apache.mina.core.session.AttributeKey;
27  import org.apache.mina.core.session.IdleStatus;
28  import org.apache.mina.core.session.IoEventType;
29  import org.apache.mina.core.session.IoSession;
30  import org.apache.mina.core.session.IoSessionConfig;
31  import org.apache.mina.core.write.DefaultWriteRequest;
32  import org.apache.mina.core.write.WriteRequest;
33  
34  /**
35   * An {@link IoFilter} that sends a keep-alive request on
36   * {@link IoEventType#SESSION_IDLE} and sends back the response for the
37   * sent keep-alive request.
38   *
39   * <h2>Interference with {@link IoSessionConfig#setIdleTime(IdleStatus, int)}</h2>
40   *
41   * This filter adjusts <tt>idleTime</tt> of the {@link IdleStatus}s that
42   * this filter is interested in automatically (e.g. {@link IdleStatus#READER_IDLE}
43   * and {@link IdleStatus#WRITER_IDLE}.)  Changing the <tt>idleTime</tt>
44   * of the {@link IdleStatus}s can lead this filter to a unexpected behavior.
45   * Please also note that any {@link IoFilter} and {@link IoHandler} behind
46   * {@link KeepAliveFilter} will not get any {@link IoEventType#SESSION_IDLE}
47   * event.  To receive the internal {@link IoEventType#SESSION_IDLE} event,
48   * you can call {@link #setForwardEvent(boolean)} with <tt>true</tt>.
49   *
50   * <h2>Implementing {@link KeepAliveMessageFactory}</h2>
51   *
52   * To use this filter, you have to provide an implementation of
53   * {@link KeepAliveMessageFactory}, which determines a received or sent
54   * message is a keep-alive message or not and creates a new keep-alive
55   * message:
56   *
57   * <table border="1">
58   * <tr>
59   * <th>Name</th><th>Description</th><th>Implementation</th>
60   * </tr>
61   * <tr valign="top">
62   * <td>Active</td>
63   * <td>You want a keep-alive request is sent when the reader is idle.
64   * Once the request is sent, the response for the request should be
65   * received within <tt>keepAliveRequestTimeout</tt> seconds.  Otherwise,
66   * the specified {@link KeepAliveRequestTimeoutHandler} will be invoked.
67   * If a keep-alive request is received, its response also should be sent back.
68   * </td>
69   * <td>Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and
70   * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must
71   * return a non-<tt>null</tt>.</td>
72   * </tr>
73   * <tr valign="top">
74   * <td>Semi-active</td>
75   * <td>You want a keep-alive request to be sent when the reader is idle.
76   * However, you don't really care if the response is received or not.
77   * If a keep-alive request is received, its response should
78   * also be sent back.
79   * </td>
80   * <td>Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and
81   * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must
82   * return a non-<tt>null</tt>, and the <tt>timeoutHandler</tt> property
83   * should be set to {@link KeepAliveRequestTimeoutHandler#NOOP},
84   * {@link KeepAliveRequestTimeoutHandler#LOG} or the custom {@link KeepAliveRequestTimeoutHandler}
85   * implementation that doesn't affect the session state nor throw an exception.
86   * </td>
87   * </tr>
88   * <tr valign="top">
89   * <td>Passive</td>
90   * <td>You don't want to send a keep-alive request by yourself, but the
91   * response should be sent back if a keep-alive request is received.</td>
92   * <td>{@link KeepAliveMessageFactory#getRequest(IoSession)} must return
93   * <tt>null</tt> and {@link KeepAliveMessageFactory#getResponse(IoSession, Object)}
94   * must return a non-<tt>null</tt>.</td>
95   * </tr>
96   * <tr valign="top">
97   * <td>Deaf Speaker</td>
98   * <td>You want a keep-alive request to be sent when the reader is idle, but
99   * you don't want to send any response back.</td>
100  * <td>{@link KeepAliveMessageFactory#getRequest(IoSession)} must return
101  * a non-<tt>null</tt>,
102  * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must
103  * return <tt>null</tt> and the <tt>timeoutHandler</tt> must be set to
104  * {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER}.</td>
105  * </tr>
106  * <tr valign="top">
107  * <td>Silent Listener</td>
108  * <td>You don't want to send a keep-alive request by yourself nor send any
109  * response back.</td>
110  * <td>Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and
111  * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must
112  * return <tt>null</tt>.</td>
113  * </tr>
114  * </table>
115  * Please note that you must implement
116  * {@link KeepAliveMessageFactory#isRequest(IoSession, Object)} and
117  * {@link KeepAliveMessageFactory#isResponse(IoSession, Object)} properly
118  * whatever case you chose.
119  *
120  * <h2>Handling timeout</h2>
121  *
122  * {@link KeepAliveFilter} will notify its {@link KeepAliveRequestTimeoutHandler}
123  * when {@link KeepAliveFilter} didn't receive the response message for a sent
124  * keep-alive message.  The default handler is {@link KeepAliveRequestTimeoutHandler#CLOSE},
125  * but you can use other presets such as {@link KeepAliveRequestTimeoutHandler#NOOP},
126  * {@link KeepAliveRequestTimeoutHandler#LOG} or {@link KeepAliveRequestTimeoutHandler#EXCEPTION}.
127  * You can even implement your own handler.
128  *
129  * <h3>Special handler: {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER}</h3>
130  *
131  * {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER} is a special handler which is
132  * dedicated for the 'deaf speaker' mode mentioned above.  Setting the
133  * <tt>timeoutHandler</tt> property to {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER}
134  * stops this filter from waiting for response messages and therefore disables
135  * response timeout detection.
136  *
137  * @author The Apache MINA Project (dev@mina.apache.org)
138  * @version $Rev: 689337 $, $Date: 2008-08-27 04:18:17 +0200 (Wed, 27 Aug 2008) $
139  * @org.apache.xbean.XBean
140  */
141 public class KeepAliveFilter extends IoFilterAdapter {
142 
143     private final AttributeKey WAITING_FOR_RESPONSE = new AttributeKey(
144             getClass(), "waitingForResponse");
145     private final AttributeKey IGNORE_READER_IDLE_ONCE = new AttributeKey(
146             getClass(), "ignoreReaderIdleOnce");
147 
148     private final KeepAliveMessageFactory messageFactory;
149     private final IdleStatus interestedIdleStatus;
150     private volatile KeepAliveRequestTimeoutHandler requestTimeoutHandler;
151     private volatile int requestInterval;
152     private volatile int requestTimeout;
153     private volatile boolean forwardEvent;
154 
155     /**
156      * Creates a new instance with the default properties.
157      * The default property values are:
158      * <ul>
159      * <li><tt>interestedIdleStatus</tt> - {@link IdleStatus#READER_IDLE}</li>
160      * <li><tt>policy</tt> = {@link KeepAliveRequestTimeoutHandler#CLOSE}</li>
161      * <li><tt>keepAliveRequestInterval</tt> - 60 (seconds)</li>
162      * <li><tt>keepAliveRequestTimeout</tt> - 30 (seconds)</li>
163      * </ul>
164      */
165     public KeepAliveFilter(KeepAliveMessageFactory messageFactory) {
166         this(messageFactory, IdleStatus.READER_IDLE, KeepAliveRequestTimeoutHandler.CLOSE);
167     }
168 
169     /**
170      * Creates a new instance with the default properties.
171      * The default property values are:
172      * <ul>
173      * <li><tt>policy</tt> = {@link KeepAliveRequestTimeoutHandler#CLOSE}</li>
174      * <li><tt>keepAliveRequestInterval</tt> - 60 (seconds)</li>
175      * <li><tt>keepAliveRequestTimeout</tt> - 30 (seconds)</li>
176      * </ul>
177      */
178     public KeepAliveFilter(
179             KeepAliveMessageFactory messageFactory,
180             IdleStatus interestedIdleStatus) {
181         this(messageFactory, interestedIdleStatus, KeepAliveRequestTimeoutHandler.CLOSE, 60, 30);
182     }
183 
184     /**
185      * Creates a new instance with the default properties.
186      * The default property values are:
187      * <ul>
188      * <li><tt>interestedIdleStatus</tt> - {@link IdleStatus#READER_IDLE}</li>
189      * <li><tt>keepAliveRequestInterval</tt> - 60 (seconds)</li>
190      * <li><tt>keepAliveRequestTimeout</tt> - 30 (seconds)</li>
191      * </ul>
192      */
193     public KeepAliveFilter(
194             KeepAliveMessageFactory messageFactory, KeepAliveRequestTimeoutHandler policy) {
195         this(messageFactory, IdleStatus.READER_IDLE, policy, 60, 30);
196     }
197 
198     /**
199      * Creates a new instance with the default properties.
200      * The default property values are:
201      * <ul>
202      * <li><tt>keepAliveRequestInterval</tt> - 60 (seconds)</li>
203      * <li><tt>keepAliveRequestTimeout</tt> - 30 (seconds)</li>
204      * </ul>
205      */
206     public KeepAliveFilter(
207             KeepAliveMessageFactory messageFactory,
208             IdleStatus interestedIdleStatus, KeepAliveRequestTimeoutHandler policy) {
209         this(messageFactory, interestedIdleStatus, policy, 60, 30);
210     }
211 
212     /**
213      * Creates a new instance.
214      */
215     public KeepAliveFilter(
216             KeepAliveMessageFactory messageFactory,
217             IdleStatus interestedIdleStatus, KeepAliveRequestTimeoutHandler policy,
218             int keepAliveRequestInterval, int keepAliveRequestTimeout) {
219         if (messageFactory == null) {
220             throw new NullPointerException("messageFactory");
221         }
222         if (interestedIdleStatus == null) {
223             throw new NullPointerException("interestedIdleStatus");
224         }
225         if (policy == null) {
226             throw new NullPointerException("policy");
227         }
228 
229         this.messageFactory = messageFactory;
230         this.interestedIdleStatus = interestedIdleStatus;
231         requestTimeoutHandler = policy;
232 
233         setRequestInterval(keepAliveRequestInterval);
234         setRequestTimeout(keepAliveRequestTimeout);
235     }
236 
237     public IdleStatus getInterestedIdleStatus() {
238         return interestedIdleStatus;
239     }
240 
241     public KeepAliveRequestTimeoutHandler getRequestTimeoutHandler() {
242         return requestTimeoutHandler;
243     }
244 
245     public void setRequestTimeoutHandler(KeepAliveRequestTimeoutHandler timeoutHandler) {
246         if (timeoutHandler == null) {
247             throw new NullPointerException("timeoutHandler");
248         }
249         requestTimeoutHandler = timeoutHandler;
250     }
251 
252     public int getRequestInterval() {
253         return requestInterval;
254     }
255 
256     public void setRequestInterval(int keepAliveRequestInterval) {
257         if (keepAliveRequestInterval <= 0) {
258             throw new IllegalArgumentException(
259                     "keepAliveRequestInterval must be a positive integer: " +
260                     keepAliveRequestInterval);
261         }
262         requestInterval = keepAliveRequestInterval;
263     }
264 
265     public int getRequestTimeout() {
266         return requestTimeout;
267     }
268 
269     public void setRequestTimeout(int keepAliveRequestTimeout) {
270         if (keepAliveRequestTimeout <= 0) {
271             throw new IllegalArgumentException(
272                     "keepAliveRequestTimeout must be a positive integer: " +
273                     keepAliveRequestTimeout);
274         }
275         requestTimeout = keepAliveRequestTimeout;
276     }
277 
278     public KeepAliveMessageFactory getMessageFactory() {
279         return messageFactory;
280     }
281 
282     /**
283      * Returns <tt>true</tt> if and only if this filter forwards
284      * a {@link IoEventType#SESSION_IDLE} event to the next filter.
285      * By default, the value of this property is <tt>false</tt>.
286      */
287     public boolean isForwardEvent() {
288         return forwardEvent;
289     }
290 
291     /**
292      * Sets if this filter needs to forward a
293      * {@link IoEventType#SESSION_IDLE} event to the next filter.
294      * By default, the value of this property is <tt>false</tt>.
295      */
296     public void setForwardEvent(boolean forwardEvent) {
297         this.forwardEvent = forwardEvent;
298     }
299 
300     @Override
301     public void onPreAdd(IoFilterChain parent, String name,
302             NextFilter nextFilter) throws Exception {
303         if (parent.contains(this)) {
304             throw new IllegalArgumentException(
305                     "You can't add the same filter instance more than once. " +
306             "Create another instance and add it.");
307         }
308     }
309 
310     @Override
311     public void onPostAdd(
312             IoFilterChain parent, String name, NextFilter nextFilter) throws Exception {
313         resetStatus(parent.getSession());
314     }
315 
316     @Override
317     public void onPostRemove(
318             IoFilterChain parent, String name, NextFilter nextFilter) throws Exception {
319         resetStatus(parent.getSession());
320     }
321 
322     @Override
323     public void messageReceived(
324             NextFilter nextFilter, IoSession session, Object message) throws Exception {
325         try {
326             if (messageFactory.isRequest(session, message)) {
327                 Object pongMessage =
328                     messageFactory.getResponse(session, message);
329 
330                 if (pongMessage != null) {
331                     nextFilter.filterWrite(
332                             session, new DefaultWriteRequest(pongMessage));
333                 }
334             }
335 
336             if (messageFactory.isResponse(session, message)) {
337                 resetStatus(session);
338             }
339         } finally {
340             if (!isKeepAliveMessage(session, message)) {
341                 nextFilter.messageReceived(session, message);
342             }
343         }
344     }
345 
346     @Override
347     public void messageSent(
348             NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
349         Object message = writeRequest.getMessage();
350         if (!isKeepAliveMessage(session, message)) {
351             nextFilter.messageSent(session, writeRequest);
352         }
353     }
354 
355     @Override
356     public void sessionIdle(
357             NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception {
358         if (status == interestedIdleStatus) {
359             if (!session.containsAttribute(WAITING_FOR_RESPONSE)) {
360                 Object pingMessage = messageFactory.getRequest(session);
361                 if (pingMessage != null) {
362                     nextFilter.filterWrite(
363                             session,
364                             new DefaultWriteRequest(pingMessage));
365 
366                     // If policy is OFF, there's no need to wait for
367                     // the response.
368                     if (getRequestTimeoutHandler() != KeepAliveRequestTimeoutHandler.DEAF_SPEAKER) {
369                         markStatus(session);
370                         if (interestedIdleStatus == IdleStatus.BOTH_IDLE) {
371                             session.setAttribute(IGNORE_READER_IDLE_ONCE);
372                         }
373                     } else {
374                         resetStatus(session);
375                     }
376                 }
377             } else {
378                 handlePingTimeout(session);
379             }
380         } else if (status == IdleStatus.READER_IDLE) {
381             if (session.removeAttribute(IGNORE_READER_IDLE_ONCE) == null) {
382                 if (session.containsAttribute(WAITING_FOR_RESPONSE)) {
383                     handlePingTimeout(session);
384                 }
385             }
386         }
387 
388         if (forwardEvent) {
389             nextFilter.sessionIdle(session, status);
390         }
391     }
392 
393     private void handlePingTimeout(IoSession session) throws Exception {
394         resetStatus(session);
395         KeepAliveRequestTimeoutHandler handler = getRequestTimeoutHandler();
396         if (handler == KeepAliveRequestTimeoutHandler.DEAF_SPEAKER) {
397             return;
398         }
399 
400         handler.keepAliveRequestTimedOut(this, session);
401     }
402 
403     private void markStatus(IoSession session) {
404         session.getConfig().setIdleTime(interestedIdleStatus, 0);
405         session.getConfig().setReaderIdleTime(getRequestTimeout());
406         session.setAttribute(WAITING_FOR_RESPONSE);
407     }
408 
409     private void resetStatus(IoSession session) {
410         session.getConfig().setReaderIdleTime(0);
411         session.getConfig().setWriterIdleTime(0);
412         session.getConfig().setIdleTime(
413                 interestedIdleStatus, getRequestInterval());
414         session.removeAttribute(WAITING_FOR_RESPONSE);
415     }
416 
417     private boolean isKeepAliveMessage(IoSession session, Object message) {
418         return messageFactory.isRequest(session, message) ||
419         messageFactory.isResponse(session, message);
420     }
421 }