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 }