Commit b4d2f11f authored by guus's avatar guus

Do not disconnect idle clients without checking if their connection is still...

Do not disconnect idle clients without checking if their connection is still active. Send IQ ping to verify this. (OF-341)

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@11583 b35dd754-fafc-0310-a699-88a17e54d16e
parent 9a5bfef8
...@@ -39,6 +39,7 @@ import org.slf4j.LoggerFactory; ...@@ -39,6 +39,7 @@ import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.IQ; import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message; import org.xmpp.packet.Message;
import org.xmpp.packet.PacketError; import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence; import org.xmpp.packet.Presence;
...@@ -667,6 +668,26 @@ public abstract class StanzaHandler { ...@@ -667,6 +668,26 @@ public abstract class StanzaHandler {
return true; return true;
} }
/**
* Obtain the address of the XMPP entity for which this StanzaHandler
* handles stanzas.
*
* Note that the value that is returned for this method can
* change over time. For example, if no session has been established yet,
* this method will return </tt>null</tt>, or, if resource binding occurs,
* the returned value might change. Values obtained from this method are
* therefore best <em>not</em> cached.
*
* @return The address of the XMPP entity for.
*/
public JID getAddress() {
if (session == null) {
return null;
}
return session.getAddress();
}
/** /**
* Returns the stream namespace. (E.g. jabber:client, jabber:server, etc.). * Returns the stream namespace. (E.g. jabber:client, jabber:server, etc.).
* *
......
...@@ -19,11 +19,19 @@ ...@@ -19,11 +19,19 @@
package org.jivesoftware.openfire.nio; package org.jivesoftware.openfire.nio;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoSession; import org.apache.mina.common.IoSession;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.handler.IQPingHandler;
import org.jivesoftware.openfire.net.ClientStanzaHandler; import org.jivesoftware.openfire.net.ClientStanzaHandler;
import org.jivesoftware.openfire.net.StanzaHandler; import org.jivesoftware.openfire.net.StanzaHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.IQ.Type;
/** /**
* ConnectionHandler that knows which subclass of {@link StanzaHandler} should * ConnectionHandler that knows which subclass of {@link StanzaHandler} should
...@@ -33,6 +41,8 @@ import org.jivesoftware.openfire.net.StanzaHandler; ...@@ -33,6 +41,8 @@ import org.jivesoftware.openfire.net.StanzaHandler;
*/ */
public class ClientConnectionHandler extends ConnectionHandler { public class ClientConnectionHandler extends ConnectionHandler {
private static final Logger Log = LoggerFactory.getLogger(ClientConnectionHandler.class);
public ClientConnectionHandler(String serverName) { public ClientConnectionHandler(String serverName) {
super(serverName); super(serverName);
} }
...@@ -48,4 +58,52 @@ public class ClientConnectionHandler extends ConnectionHandler { ...@@ -48,4 +58,52 @@ public class ClientConnectionHandler extends ConnectionHandler {
int getMaxIdleTime() { int getMaxIdleTime() {
return JiveGlobals.getIntProperty("xmpp.client.idle", 6 * 60 * 1000) / 1000; return JiveGlobals.getIntProperty("xmpp.client.idle", 6 * 60 * 1000) / 1000;
} }
/**
* In addition to the functionality provided by the parent class, this
* method will send XMPP ping requests to the remote entity on every first
* invocation of this method (which will occur after a period of half the
* allowed connection idle time has passed, without any IO).
*
* XMPP entities must respond with either an IQ result or an IQ error
* (feature-unavailable) stanza upon receiving the XMPP ping stanza. Both
* responses will be received by Openfire and will cause the connection idle
* count to be reset.
*
* Entities that do not respond to the IQ Ping stanzas can be considered
* dead, and their connection will be closed by the parent class
* implementation on the second invocation of this method.
*
* Note that whitespace pings that are sent by XMPP entities will also cause
* the connection idle count to be reset.
*
* @see ConnectionHandler#sessionIdle(IoSession, IdleStatus)
*/
@Override
public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
super.sessionIdle(session, status);
if (session.getIdleCount(status) == 1) {
final ClientStanzaHandler handler = (ClientStanzaHandler) session.getAttribute(HANDLER);
final JID entity = handler.getAddress();
if (entity != null) {
// Ping the connection to see if it is alive.
final IQ pingRequest = new IQ(Type.get);
pingRequest.setChildElement("ping",
IQPingHandler.NAMESPACE);
pingRequest.setFrom(serverName);
pingRequest.setTo(entity);
// Get the connection for this session
final Connection connection = (Connection) session.getAttribute(CONNECTION);
if (Log.isDebugEnabled()) {
Log.debug("ConnectionHandler: Pinging connection that has been idle: " + connection);
}
connection.deliver(pingRequest);
}
}
}
} }
...@@ -52,8 +52,8 @@ public abstract class ConnectionHandler extends IoHandlerAdapter { ...@@ -52,8 +52,8 @@ public abstract class ConnectionHandler extends IoHandlerAdapter {
*/ */
static final String CHARSET = "UTF-8"; static final String CHARSET = "UTF-8";
static final String XML_PARSER = "XML-PARSER"; static final String XML_PARSER = "XML-PARSER";
private static final String HANDLER = "HANDLER"; protected static final String HANDLER = "HANDLER";
private static final String CONNECTION = "CONNECTION"; protected static final String CONNECTION = "CONNECTION";
protected String serverName; protected String serverName;
private static Map<Integer, XMPPPacketReader> parsers = new ConcurrentHashMap<Integer, XMPPPacketReader>(); private static Map<Integer, XMPPPacketReader> parsers = new ConcurrentHashMap<Integer, XMPPPacketReader>();
...@@ -78,14 +78,17 @@ public abstract class ConnectionHandler extends IoHandlerAdapter { ...@@ -78,14 +78,17 @@ public abstract class ConnectionHandler extends IoHandlerAdapter {
public void sessionOpened(IoSession session) throws Exception { public void sessionOpened(IoSession session) throws Exception {
// Create a new XML parser for the new connection. The parser will be used by the XMPPDecoder filter. // Create a new XML parser for the new connection. The parser will be used by the XMPPDecoder filter.
XMLLightweightParser parser = new XMLLightweightParser(CHARSET); final XMLLightweightParser parser = new XMLLightweightParser(CHARSET);
session.setAttribute(XML_PARSER, parser); session.setAttribute(XML_PARSER, parser);
// Create a new NIOConnection for the new session // Create a new NIOConnection for the new session
NIOConnection connection = createNIOConnection(session); final NIOConnection connection = createNIOConnection(session);
session.setAttribute(CONNECTION, connection); session.setAttribute(CONNECTION, connection);
session.setAttribute(HANDLER, createStanzaHandler(connection)); session.setAttribute(HANDLER, createStanzaHandler(connection));
// Set the max time a connection can be idle before closing it // Set the max time a connection can be idle before closing it. This amount of seconds
int idleTime = getMaxIdleTime(); // is divided in two, as Openfire will ping idle clients first (at 50% of the max idle time)
// before disconnecting them (at 100% of the max idle time). This prevents Openfire from
// removing connections without warning.
final int idleTime = getMaxIdleTime() / 2;
if (idleTime > 0) { if (idleTime > 0) {
session.setIdleTime(IdleStatus.READER_IDLE, idleTime); session.setIdleTime(IdleStatus.READER_IDLE, idleTime);
} }
...@@ -98,15 +101,32 @@ public abstract class ConnectionHandler extends IoHandlerAdapter { ...@@ -98,15 +101,32 @@ public abstract class ConnectionHandler extends IoHandlerAdapter {
connection.close(); connection.close();
} }
/**
* Invoked when a MINA session has been idle for half of the allowed XMPP
* session idle time as specified by {@link #getMaxIdleTime()}. This method
* will be invoked each time that such a period passes (even if no IO has
* occurred in between).
*
* Openfire will disconnect a session the second time this method is
* invoked, if no IO has occurred between the first and second invocation.
* This allows extensions of this class to use the first invocation to check
* for livelyness of the MINA session (e.g by polling the remote entity, as
* {@link ClientConnectionHandler} does).
*
* @see org.apache.mina.common.IoHandlerAdapter#sessionIdle(org.apache.mina.common.IoSession,
* org.apache.mina.common.IdleStatus)
*/
public void sessionIdle(IoSession session, IdleStatus status) throws Exception { public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
if (session.getIdleCount(status) > 1) {
// Get the connection for this session // Get the connection for this session
Connection connection = (Connection) session.getAttribute(CONNECTION); final Connection connection = (Connection) session.getAttribute(CONNECTION);
// Close idle connection // Close idle connection
if (Log.isDebugEnabled()) { if (Log.isDebugEnabled()) {
Log.debug("ConnectionHandler: Closing connection that has been idle: " + connection); Log.debug("ConnectionHandler: Closing connection that has been idle: " + connection);
} }
connection.close(); connection.close();
} }
}
public void exceptionCaught(IoSession session, Throwable cause) throws Exception { public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
if (cause instanceof IOException) { if (cause instanceof IOException) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment