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 <a href="http://mina.apache.org">Apache MINA Project</a>
138  * @org.apache.xbean.XBean
139  */
140 public class KeepAliveFilter extends IoFilterAdapter {
141 
142     private final AttributeKey WAITING_FOR_RESPONSE = new AttributeKey(getClass(), "waitingForResponse");
143 
144     private final AttributeKey IGNORE_READER_IDLE_ONCE = new AttributeKey(getClass(), "ignoreReaderIdleOnce");
145 
146     private final KeepAliveMessageFactory messageFactory;
147 
148     private final IdleStatus interestedIdleStatus;
149 
150     private volatile KeepAliveRequestTimeoutHandler requestTimeoutHandler;
151 
152     private volatile int requestInterval;
153 
154     private volatile int requestTimeout;
155 
156     private volatile boolean forwardEvent;
157 
158     /**
159      * Creates a new instance with the default properties.
160      * The default property values are:
161      * <ul>
162      * <li><tt>interestedIdleStatus</tt> - {@link IdleStatus#READER_IDLE}</li>
163      * <li><tt>policy</tt> = {@link KeepAliveRequestTimeoutHandler#CLOSE}</li>
164      * <li><tt>keepAliveRequestInterval</tt> - 60 (seconds)</li>
165      * <li><tt>keepAliveRequestTimeout</tt> - 30 (seconds)</li>
166      * </ul>
167      */
168     public KeepAliveFilter(KeepAliveMessageFactory messageFactory) {
169         this(messageFactory, IdleStatus.READER_IDLE, KeepAliveRequestTimeoutHandler.CLOSE);
170     }
171 
172     /**
173      * Creates a new instance with the default properties.
174      * The default property values are:
175      * <ul>
176      * <li><tt>policy</tt> = {@link KeepAliveRequestTimeoutHandler#CLOSE}</li>
177      * <li><tt>keepAliveRequestInterval</tt> - 60 (seconds)</li>
178      * <li><tt>keepAliveRequestTimeout</tt> - 30 (seconds)</li>
179      * </ul>
180      */
181     public KeepAliveFilter(KeepAliveMessageFactory messageFactory, IdleStatus interestedIdleStatus) {
182         this(messageFactory, interestedIdleStatus, KeepAliveRequestTimeoutHandler.CLOSE, 60, 30);
183     }
184 
185     /**
186      * Creates a new instance with the default properties.
187      * The default property values are:
188      * <ul>
189      * <li><tt>interestedIdleStatus</tt> - {@link IdleStatus#READER_IDLE}</li>
190      * <li><tt>keepAliveRequestInterval</tt> - 60 (seconds)</li>
191      * <li><tt>keepAliveRequestTimeout</tt> - 30 (seconds)</li>
192      * </ul>
193      */
194     public KeepAliveFilter(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(KeepAliveMessageFactory messageFactory, IdleStatus interestedIdleStatus,
207             KeepAliveRequestTimeoutHandler policy) {
208         this(messageFactory, interestedIdleStatus, policy, 60, 30);
209     }
210 
211     /**
212      * Creates a new instance.
213      */
214     public KeepAliveFilter(KeepAliveMessageFactory messageFactory, IdleStatus interestedIdleStatus,
215             KeepAliveRequestTimeoutHandler policy, int keepAliveRequestInterval, int keepAliveRequestTimeout) {
216         if (messageFactory == null) {
217             throw new IllegalArgumentException("messageFactory");
218         }
219         if (interestedIdleStatus == null) {
220             throw new IllegalArgumentException("interestedIdleStatus");
221         }
222         if (policy == null) {
223             throw new IllegalArgumentException("policy");
224         }
225 
226         this.messageFactory = messageFactory;
227         this.interestedIdleStatus = interestedIdleStatus;
228         requestTimeoutHandler = policy;
229 
230         setRequestInterval(keepAliveRequestInterval);
231         setRequestTimeout(keepAliveRequestTimeout);
232     }
233 
234     public IdleStatus getInterestedIdleStatus() {
235         return interestedIdleStatus;
236     }
237 
238     public KeepAliveRequestTimeoutHandler getRequestTimeoutHandler() {
239         return requestTimeoutHandler;
240     }
241 
242     public void setRequestTimeoutHandler(KeepAliveRequestTimeoutHandler timeoutHandler) {
243         if (timeoutHandler == null) {
244             throw new IllegalArgumentException("timeoutHandler");
245         }
246         requestTimeoutHandler = timeoutHandler;
247     }
248 
249     public int getRequestInterval() {
250         return requestInterval;
251     }
252 
253     public void setRequestInterval(int keepAliveRequestInterval) {
254         if (keepAliveRequestInterval <= 0) {
255             throw new IllegalArgumentException("keepAliveRequestInterval must be a positive integer: "
256                     + keepAliveRequestInterval);
257         }
258         requestInterval = keepAliveRequestInterval;
259     }
260 
261     public int getRequestTimeout() {
262         return requestTimeout;
263     }
264 
265     public void setRequestTimeout(int keepAliveRequestTimeout) {
266         if (keepAliveRequestTimeout <= 0) {
267             throw new IllegalArgumentException("keepAliveRequestTimeout must be a positive integer: "
268                     + keepAliveRequestTimeout);
269         }
270         requestTimeout = keepAliveRequestTimeout;
271     }
272 
273     public KeepAliveMessageFactory getMessageFactory() {
274         return messageFactory;
275     }
276 
277     /**
278      * Returns <tt>true</tt> if and only if this filter forwards
279      * a {@link IoEventType#SESSION_IDLE} event to the next filter.
280      * By default, the value of this property is <tt>false</tt>.
281      */
282     public boolean isForwardEvent() {
283         return forwardEvent;
284     }
285 
286     /**
287      * Sets if this filter needs to forward a
288      * {@link IoEventType#SESSION_IDLE} event to the next filter.
289      * By default, the value of this property is <tt>false</tt>.
290      */
291     public void setForwardEvent(boolean forwardEvent) {
292         this.forwardEvent = forwardEvent;
293     }
294 
295     @Override
296     public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception {
297         if (parent.contains(this)) {
298             throw new IllegalArgumentException("You can't add the same filter instance more than once. "
299                     + "Create another instance and add it.");
300         }
301     }
302 
303     @Override
304     public void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception {
305         resetStatus(parent.getSession());
306     }
307 
308     @Override
309     public void onPostRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception {
310         resetStatus(parent.getSession());
311     }
312 
313     @Override
314     public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
315         try {
316             if (messageFactory.isRequest(session, message)) {
317                 Object pongMessage = messageFactory.getResponse(session, message);
318 
319                 if (pongMessage != null) {
320                     nextFilter.filterWrite(session, new DefaultWriteRequest(pongMessage));
321                 }
322             }
323 
324             if (messageFactory.isResponse(session, message)) {
325                 resetStatus(session);
326             }
327         } finally {
328             if (!isKeepAliveMessage(session, message)) {
329                 nextFilter.messageReceived(session, message);
330             }
331         }
332     }
333 
334     @Override
335     public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
336         Object message = writeRequest.getMessage();
337         if (!isKeepAliveMessage(session, message)) {
338             nextFilter.messageSent(session, writeRequest);
339         }
340     }
341 
342     @Override
343     public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception {
344         if (status == interestedIdleStatus) {
345             if (!session.containsAttribute(WAITING_FOR_RESPONSE)) {
346                 Object pingMessage = messageFactory.getRequest(session);
347                 if (pingMessage != null) {
348                     nextFilter.filterWrite(session, new DefaultWriteRequest(pingMessage));
349 
350                     // If policy is OFF, there's no need to wait for
351                     // the response.
352                     if (getRequestTimeoutHandler() != KeepAliveRequestTimeoutHandler.DEAF_SPEAKER) {
353                         markStatus(session);
354                         if (interestedIdleStatus == IdleStatus.BOTH_IDLE) {
355                             session.setAttribute(IGNORE_READER_IDLE_ONCE);
356                         }
357                     } else {
358                         resetStatus(session);
359                     }
360                 }
361             } else {
362                 handlePingTimeout(session);
363             }
364         } else if (status == IdleStatus.READER_IDLE) {
365             if (session.removeAttribute(IGNORE_READER_IDLE_ONCE) == null) {
366                 if (session.containsAttribute(WAITING_FOR_RESPONSE)) {
367                     handlePingTimeout(session);
368                 }
369             }
370         }
371 
372         if (forwardEvent) {
373             nextFilter.sessionIdle(session, status);
374         }
375     }
376 
377     private void handlePingTimeout(IoSession session) throws Exception {
378         resetStatus(session);
379         KeepAliveRequestTimeoutHandler handler = getRequestTimeoutHandler();
380         if (handler == KeepAliveRequestTimeoutHandler.DEAF_SPEAKER) {
381             return;
382         }
383 
384         handler.keepAliveRequestTimedOut(this, session);
385     }
386 
387     private void markStatus(IoSession session) {
388         session.getConfig().setIdleTime(interestedIdleStatus, 0);
389         session.getConfig().setReaderIdleTime(getRequestTimeout());
390         session.setAttribute(WAITING_FOR_RESPONSE);
391     }
392 
393     private void resetStatus(IoSession session) {
394         session.getConfig().setReaderIdleTime(0);
395         session.getConfig().setWriterIdleTime(0);
396         session.getConfig().setIdleTime(interestedIdleStatus, getRequestInterval());
397         session.removeAttribute(WAITING_FOR_RESPONSE);
398     }
399 
400     private boolean isKeepAliveMessage(IoSession session, Object message) {
401         return messageFactory.isRequest(session, message) || messageFactory.isResponse(session, message);
402     }
403 }