001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.filter.keepalive; 021 022import org.apache.mina.core.filterchain.IoFilter; 023import org.apache.mina.core.filterchain.IoFilterAdapter; 024import org.apache.mina.core.filterchain.IoFilterChain; 025import org.apache.mina.core.service.IoHandler; 026import org.apache.mina.core.session.AttributeKey; 027import org.apache.mina.core.session.IdleStatus; 028import org.apache.mina.core.session.IoEventType; 029import org.apache.mina.core.session.IoSession; 030import org.apache.mina.core.session.IoSessionConfig; 031import org.apache.mina.core.write.DefaultWriteRequest; 032import org.apache.mina.core.write.WriteRequest; 033 034/** 035 * An {@link IoFilter} that sends a keep-alive request on 036 * {@link IoEventType#SESSION_IDLE} and sends back the response for the 037 * sent keep-alive request. 038 * 039 * <h2>Interference with {@link IoSessionConfig#setIdleTime(IdleStatus, int)}</h2> 040 * 041 * This filter adjusts <tt>idleTime</tt> of the {@link IdleStatus}s that 042 * this filter is interested in automatically (e.g. {@link IdleStatus#READER_IDLE} 043 * and {@link IdleStatus#WRITER_IDLE}.) Changing the <tt>idleTime</tt> 044 * of the {@link IdleStatus}s can lead this filter to a unexpected behavior. 045 * Please also note that any {@link IoFilter} and {@link IoHandler} behind 046 * {@link KeepAliveFilter} will not get any {@link IoEventType#SESSION_IDLE} 047 * event. To receive the internal {@link IoEventType#SESSION_IDLE} event, 048 * you can call {@link #setForwardEvent(boolean)} with <tt>true</tt>. 049 * 050 * <h2>Implementing {@link KeepAliveMessageFactory}</h2> 051 * 052 * To use this filter, you have to provide an implementation of 053 * {@link KeepAliveMessageFactory}, which determines a received or sent 054 * message is a keep-alive message or not and creates a new keep-alive 055 * message: 056 * 057 * <table border="1" summary="Message"> 058 * <tr> 059 * <th>Name</th><th>Description</th><th>Implementation</th> 060 * </tr> 061 * <tr valign="top"> 062 * <td>Active</td> 063 * <td> 064 * You want a keep-alive request is sent when the reader is idle. 065 * Once the request is sent, the response for the request should be 066 * received within <tt>keepAliveRequestTimeout</tt> seconds. Otherwise, 067 * the specified {@link KeepAliveRequestTimeoutHandler} will be invoked. 068 * If a keep-alive request is received, its response also should be sent back. 069 * </td> 070 * <td> 071 * Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and 072 * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must 073 * return a non-<tt>null</tt>. 074 * </td> 075 * </tr> 076 * <tr valign="top"> 077 * <td>Semi-active</td> 078 * <td> 079 * You want a keep-alive request to be sent when the reader is idle. 080 * However, you don't really care if the response is received or not. 081 * If a keep-alive request is received, its response should 082 * also be sent back. 083 * </td> 084 * <td> 085 * Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and 086 * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must 087 * return a non-<tt>null</tt>, and the <tt>timeoutHandler</tt> property 088 * should be set to {@link KeepAliveRequestTimeoutHandler#NOOP}, 089 * {@link KeepAliveRequestTimeoutHandler#LOG} or the custom {@link KeepAliveRequestTimeoutHandler} 090 * implementation that doesn't affect the session state nor throw an exception. 091 * </td> 092 * </tr> 093 * <tr valign="top"> 094 * <td>Passive</td> 095 * <td> 096 * You don't want to send a keep-alive request by yourself, but the 097 * response should be sent back if a keep-alive request is received. 098 * </td> 099 * <td> 100 * {@link KeepAliveMessageFactory#getRequest(IoSession)} must return 101 * <tt>null</tt> and {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} 102 * must return a non-<tt>null</tt>. 103 * </td> 104 * </tr> 105 * <tr valign="top"> 106 * <td>Deaf Speaker</td> 107 * <td> 108 * You want a keep-alive request to be sent when the reader is idle, but 109 * you don't want to send any response back. 110 * </td> 111 * <td> 112 * {@link KeepAliveMessageFactory#getRequest(IoSession)} must return 113 * a non-<tt>null</tt>, 114 * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must 115 * return <tt>null</tt> and the <tt>timeoutHandler</tt> must be set to 116 * {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER}. 117 * </td> 118 * </tr> 119 * <tr valign="top"> 120 * <td>Silent Listener</td> 121 * <td> 122 * You don't want to send a keep-alive request by yourself nor send any 123 * response back. 124 * </td> 125 * <td> 126 * Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and 127 * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must 128 * return <tt>null</tt>. 129 * </td> 130 * </tr> 131 * </table> 132 * 133 * Please note that you must implement 134 * {@link KeepAliveMessageFactory#isRequest(IoSession, Object)} and 135 * {@link KeepAliveMessageFactory#isResponse(IoSession, Object)} properly 136 * whatever case you chose. 137 * 138 * <h2>Handling timeout</h2> 139 * 140 * {@link KeepAliveFilter} will notify its {@link KeepAliveRequestTimeoutHandler} 141 * when {@link KeepAliveFilter} didn't receive the response message for a sent 142 * keep-alive message. The default handler is {@link KeepAliveRequestTimeoutHandler#CLOSE}, 143 * but you can use other presets such as {@link KeepAliveRequestTimeoutHandler#NOOP}, 144 * {@link KeepAliveRequestTimeoutHandler#LOG} or {@link KeepAliveRequestTimeoutHandler#EXCEPTION}. 145 * You can even implement your own handler. 146 * 147 * <h3>Special handler: {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER}</h3> 148 * 149 * {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER} is a special handler which is 150 * dedicated for the 'deaf speaker' mode mentioned above. Setting the 151 * <tt>timeoutHandler</tt> property to {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER} 152 * stops this filter from waiting for response messages and therefore disables 153 * response timeout detection. 154 * 155 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 156 * @org.apache.xbean.XBean 157 */ 158public class KeepAliveFilter extends IoFilterAdapter { 159 160 private final AttributeKey WAITING_FOR_RESPONSE = new AttributeKey(getClass(), "waitingForResponse"); 161 162 private final AttributeKey IGNORE_READER_IDLE_ONCE = new AttributeKey(getClass(), "ignoreReaderIdleOnce"); 163 164 private final KeepAliveMessageFactory messageFactory; 165 166 private final IdleStatus interestedIdleStatus; 167 168 private volatile KeepAliveRequestTimeoutHandler requestTimeoutHandler; 169 170 private volatile int requestInterval; 171 172 private volatile int requestTimeout; 173 174 private volatile boolean forwardEvent; 175 176 /** 177 * Creates a new instance with the default properties. 178 * The default property values are: 179 * <ul> 180 * <li><tt>interestedIdleStatus</tt> - {@link IdleStatus#READER_IDLE}</li> 181 * <li><tt>policy</tt> = {@link KeepAliveRequestTimeoutHandler#CLOSE}</li> 182 * <li><tt>keepAliveRequestInterval</tt> - 60 (seconds)</li> 183 * <li><tt>keepAliveRequestTimeout</tt> - 30 (seconds)</li> 184 * </ul> 185 * 186 * @param messageFactory The message factory to use 187 */ 188 public KeepAliveFilter(KeepAliveMessageFactory messageFactory) { 189 this(messageFactory, IdleStatus.READER_IDLE, KeepAliveRequestTimeoutHandler.CLOSE); 190 } 191 192 /** 193 * Creates a new instance with the default properties. 194 * The default property values are: 195 * <ul> 196 * <li><tt>policy</tt> = {@link KeepAliveRequestTimeoutHandler#CLOSE}</li> 197 * <li><tt>keepAliveRequestInterval</tt> - 60 (seconds)</li> 198 * <li><tt>keepAliveRequestTimeout</tt> - 30 (seconds)</li> 199 * </ul> 200 * 201 * @param messageFactory The message factory to use 202 * @param interestedIdleStatus The IdleStatus the filter is interested in 203 */ 204 public KeepAliveFilter(KeepAliveMessageFactory messageFactory, IdleStatus interestedIdleStatus) { 205 this(messageFactory, interestedIdleStatus, KeepAliveRequestTimeoutHandler.CLOSE, 60, 30); 206 } 207 208 /** 209 * Creates a new instance with the default properties. 210 * The default property values are: 211 * <ul> 212 * <li><tt>interestedIdleStatus</tt> - {@link IdleStatus#READER_IDLE}</li> 213 * <li><tt>keepAliveRequestInterval</tt> - 60 (seconds)</li> 214 * <li><tt>keepAliveRequestTimeout</tt> - 30 (seconds)</li> 215 * </ul> 216 * 217 * @param messageFactory The message factory to use 218 * @param policy The TimeOut handler policy 219 */ 220 public KeepAliveFilter(KeepAliveMessageFactory messageFactory, KeepAliveRequestTimeoutHandler policy) { 221 this(messageFactory, IdleStatus.READER_IDLE, policy, 60, 30); 222 } 223 224 /** 225 * Creates a new instance with the default properties. 226 * The default property values are: 227 * <ul> 228 * <li><tt>keepAliveRequestInterval</tt> - 60 (seconds)</li> 229 * <li><tt>keepAliveRequestTimeout</tt> - 30 (seconds)</li> 230 * </ul> 231 * 232 * @param messageFactory The message factory to use 233 * @param interestedIdleStatus The IdleStatus the filter is interested in 234 * @param policy The TimeOut handler policy 235 */ 236 public KeepAliveFilter(KeepAliveMessageFactory messageFactory, IdleStatus interestedIdleStatus, 237 KeepAliveRequestTimeoutHandler policy) { 238 this(messageFactory, interestedIdleStatus, policy, 60, 30); 239 } 240 241 /** 242 * Creates a new instance. 243 * 244 * @param messageFactory The message factory to use 245 * @param interestedIdleStatus The IdleStatus the filter is interested in 246 * @param policy The TimeOut handler policy 247 * @param keepAliveRequestInterval the interval to use 248 * @param keepAliveRequestTimeout The timeout to use 249 */ 250 public KeepAliveFilter(KeepAliveMessageFactory messageFactory, IdleStatus interestedIdleStatus, 251 KeepAliveRequestTimeoutHandler policy, int keepAliveRequestInterval, int keepAliveRequestTimeout) { 252 if (messageFactory == null) { 253 throw new IllegalArgumentException("messageFactory"); 254 } 255 256 if (interestedIdleStatus == null) { 257 throw new IllegalArgumentException("interestedIdleStatus"); 258 } 259 260 if (policy == null) { 261 throw new IllegalArgumentException("policy"); 262 } 263 264 this.messageFactory = messageFactory; 265 this.interestedIdleStatus = interestedIdleStatus; 266 requestTimeoutHandler = policy; 267 268 setRequestInterval(keepAliveRequestInterval); 269 setRequestTimeout(keepAliveRequestTimeout); 270 } 271 272 /** 273 * @return The {@link IdleStatus} 274 */ 275 public IdleStatus getInterestedIdleStatus() { 276 return interestedIdleStatus; 277 } 278 279 /** 280 * @return The timeout request handler 281 */ 282 public KeepAliveRequestTimeoutHandler getRequestTimeoutHandler() { 283 return requestTimeoutHandler; 284 } 285 286 /** 287 * Set the timeout handler 288 * 289 * @param timeoutHandler The instance of {@link KeepAliveRequestTimeoutHandler} to use 290 */ 291 public void setRequestTimeoutHandler(KeepAliveRequestTimeoutHandler timeoutHandler) { 292 if (timeoutHandler == null) { 293 throw new IllegalArgumentException("timeoutHandler"); 294 } 295 requestTimeoutHandler = timeoutHandler; 296 } 297 298 /** 299 * @return the interval for keep alive messages 300 */ 301 public int getRequestInterval() { 302 return requestInterval; 303 } 304 305 /** 306 * Sets the interval for keepAlive messages 307 * 308 * @param keepAliveRequestInterval the interval to set 309 */ 310 public void setRequestInterval(int keepAliveRequestInterval) { 311 if (keepAliveRequestInterval <= 0) { 312 throw new IllegalArgumentException("keepAliveRequestInterval must be a positive integer: " 313 + keepAliveRequestInterval); 314 } 315 316 requestInterval = keepAliveRequestInterval; 317 } 318 319 /** 320 * @return The timeout 321 */ 322 public int getRequestTimeout() { 323 return requestTimeout; 324 } 325 326 /** 327 * Sets the timeout 328 * 329 * @param keepAliveRequestTimeout The timeout to set 330 */ 331 public void setRequestTimeout(int keepAliveRequestTimeout) { 332 if (keepAliveRequestTimeout <= 0) { 333 throw new IllegalArgumentException("keepAliveRequestTimeout must be a positive integer: " 334 + keepAliveRequestTimeout); 335 } 336 337 requestTimeout = keepAliveRequestTimeout; 338 } 339 340 /** 341 * @return The message factory 342 */ 343 public KeepAliveMessageFactory getMessageFactory() { 344 return messageFactory; 345 } 346 347 /** 348 * @return <tt>true</tt> if and only if this filter forwards 349 * a {@link IoEventType#SESSION_IDLE} event to the next filter. 350 * By default, the value of this property is <tt>false</tt>. 351 */ 352 public boolean isForwardEvent() { 353 return forwardEvent; 354 } 355 356 /** 357 * Sets if this filter needs to forward a 358 * {@link IoEventType#SESSION_IDLE} event to the next filter. 359 * By default, the value of this property is <tt>false</tt>. 360 * 361 * @param forwardEvent a flag set to tell if the filter has to forward a {@link IoEventType#SESSION_IDLE} event 362 */ 363 public void setForwardEvent(boolean forwardEvent) { 364 this.forwardEvent = forwardEvent; 365 } 366 367 /** 368 * {@inheritDoc} 369 */ 370 @Override 371 public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { 372 if (parent.contains(this)) { 373 throw new IllegalArgumentException("You can't add the same filter instance more than once. " 374 + "Create another instance and add it."); 375 } 376 } 377 378 /** 379 * {@inheritDoc} 380 */ 381 @Override 382 public void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { 383 resetStatus(parent.getSession()); 384 } 385 386 /** 387 * {@inheritDoc} 388 */ 389 @Override 390 public void onPostRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { 391 resetStatus(parent.getSession()); 392 } 393 394 /** 395 * {@inheritDoc} 396 */ 397 @Override 398 public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { 399 try { 400 if (messageFactory.isRequest(session, message)) { 401 Object pongMessage = messageFactory.getResponse(session, message); 402 403 if (pongMessage != null) { 404 nextFilter.filterWrite(session, new DefaultWriteRequest(pongMessage)); 405 } 406 } 407 408 if (messageFactory.isResponse(session, message)) { 409 resetStatus(session); 410 } 411 } finally { 412 if (!isKeepAliveMessage(session, message)) { 413 nextFilter.messageReceived(session, message); 414 } 415 } 416 } 417 418 /** 419 * {@inheritDoc} 420 */ 421 @Override 422 public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { 423 Object message = writeRequest.getMessage(); 424 425 if (!isKeepAliveMessage(session, message)) { 426 nextFilter.messageSent(session, writeRequest); 427 } 428 } 429 430 /** 431 * {@inheritDoc} 432 */ 433 @Override 434 public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { 435 if (status == interestedIdleStatus) { 436 if (!session.containsAttribute(WAITING_FOR_RESPONSE)) { 437 Object pingMessage = messageFactory.getRequest(session); 438 439 if (pingMessage != null) { 440 nextFilter.filterWrite(session, new DefaultWriteRequest(pingMessage)); 441 442 // If policy is OFF, there's no need to wait for 443 // the response. 444 if (getRequestTimeoutHandler() != KeepAliveRequestTimeoutHandler.DEAF_SPEAKER) { 445 markStatus(session); 446 if (interestedIdleStatus == IdleStatus.BOTH_IDLE) { 447 session.setAttribute(IGNORE_READER_IDLE_ONCE); 448 } 449 } else { 450 resetStatus(session); 451 } 452 } 453 } else { 454 handlePingTimeout(session); 455 } 456 } else if (status == IdleStatus.READER_IDLE) { 457 if (session.removeAttribute(IGNORE_READER_IDLE_ONCE) == null) { 458 if (session.containsAttribute(WAITING_FOR_RESPONSE)) { 459 handlePingTimeout(session); 460 } 461 } 462 } 463 464 if (forwardEvent) { 465 nextFilter.sessionIdle(session, status); 466 } 467 } 468 469 private void handlePingTimeout(IoSession session) throws Exception { 470 resetStatus(session); 471 KeepAliveRequestTimeoutHandler handler = getRequestTimeoutHandler(); 472 if (handler == KeepAliveRequestTimeoutHandler.DEAF_SPEAKER) { 473 return; 474 } 475 476 handler.keepAliveRequestTimedOut(this, session); 477 } 478 479 private void markStatus(IoSession session) { 480 session.getConfig().setIdleTime(interestedIdleStatus, 0); 481 session.getConfig().setReaderIdleTime(getRequestTimeout()); 482 session.setAttribute(WAITING_FOR_RESPONSE); 483 } 484 485 private void resetStatus(IoSession session) { 486 session.getConfig().setReaderIdleTime(0); 487 session.getConfig().setWriterIdleTime(0); 488 session.getConfig().setIdleTime(interestedIdleStatus, getRequestInterval()); 489 session.removeAttribute(WAITING_FOR_RESPONSE); 490 } 491 492 private boolean isKeepAliveMessage(IoSession session, Object message) { 493 return messageFactory.isRequest(session, message) || messageFactory.isResponse(session, message); 494 } 495}