/* * Copyright 2009 Thomas Bocek * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package net.tomp2p.connection2; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.concurrent.GenericFutureListener; import net.tomp2p.futures.FutureResponse; import net.tomp2p.message.Message2; import net.tomp2p.message.MessageID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Is able to send UDP messages (as a request) and processes incoming replies. It is important that this class handles * close() because if we shutdown the connections, the we need to notify the futures. In case of error set the peer to * offline. A similar class is {@link RequestHandlerTCP}, which is used for TCP. * * @author Thomas Bocek * @param <K> * The type of future to handle */ public class RequestHandler<K extends FutureResponse> extends SimpleChannelInboundHandler<Message2> { private static final Logger LOG = LoggerFactory.getLogger(RequestHandler.class); // The future response which is currently be waited for private final K futureResponse; // The node this request handler is associated with private final PeerBean peerBean; private final ConnectionBean connectionBean; private final Message2 message; private final MessageID sendMessageID; // modifiable variables private final int idleTCPSeconds; // = ConnectionBean.DEFAULT_TCP_IDLE_SECONDS; private final int idleUDPSeconds; // = ConnectionBean.DEFAULT_UDP_IDLE_SECONDS; private final int connectionTimeoutTCPMillis; // = ConnectionBean.DEFAULT_CONNECTION_TIMEOUT_TCP; /** * Create a request handler that can send UDP messages. * * @param futureResponse * The future that will be called when we get an answer * @param peerBean * The peer bean * @param connectionBean * The connection bean * @param configuration * the client side connection configuration */ public RequestHandler(final K futureResponse, final PeerBean peerBean, final ConnectionBean connectionBean, final ConnectionConfiguration configuration) { this.peerBean = peerBean; this.connectionBean = connectionBean; this.futureResponse = futureResponse; this.message = futureResponse.getRequest(); this.sendMessageID = new MessageID(message); this.idleTCPSeconds = configuration.idleTCPSeconds(); this.idleUDPSeconds = configuration.idleUDPSeconds(); this.connectionTimeoutTCPMillis = configuration.connectionTimeoutTCPMillis(); } /** * @return The future response that will be called when we get an answer */ public K futureResponse() { return futureResponse; } /** * @return The peer bean */ public PeerBean peerBean() { return peerBean; } /** * @return The connection bean */ public ConnectionBean connectionBean() { return connectionBean; } /** * @return The time that a TCP connection can be idle */ public int idleTCPSeconds() { return idleTCPSeconds; } /** * @return The time that a UDP connection can be idle */ public int idleUDPSeconds() { return idleUDPSeconds; } /** * @return The time a TCP connection is allowed to be established */ public int connectionTimeoutTCPMillis() { return connectionTimeoutTCPMillis; } /** * Send a UDP message and expect a reply. * * @param channelCreator * The channel creator will create a UDP connection * @return The future that was added in the constructor */ public K sendUDP(final ChannelCreator channelCreator) { connectionBean.sender().sendUDP(this, futureResponse, message, channelCreator, idleUDPSeconds, false); return futureResponse; } /** * Send a UDP message and don't expect a reply. * * @param channelCreator * The channel creator will create a UDP connection * @return The future that was added in the constructor */ public K fireAndForgetUDP(final ChannelCreator channelCreator) { connectionBean.sender().sendUDP(null, futureResponse, message, channelCreator, 0, false); return futureResponse; } /** * Broadcast a UDP message (layer 2) and expect a reply. * * @param channelCreator * The channel creator will create a UDP connection * @return The future that was added in the constructor */ public K sendBroadcastUDP(final ChannelCreator channelCreator) { connectionBean.sender().sendUDP(this, futureResponse, message, channelCreator, idleUDPSeconds, true); return futureResponse; } /** * Send a TCP message and expect a reply. * * @param channelCreator * The channel creator will create a TCP connection * @return The future that was added in the constructor */ public K sendTCP(final ChannelCreator channelCreator) { connectionBean.sender().sendTCP(this, futureResponse, message, channelCreator, idleTCPSeconds, connectionTimeoutTCPMillis, null); return futureResponse; } /** * Send a TCP message and expect a reply. * * @param channelCreator * The channel creator will create a TCP connection * @return The future that was added in the constructor */ public K sendTCP(final ChannelCreator channelCreator, final PeerConnection peerConnection) { connectionBean.sender().sendTCP(this, futureResponse, message, channelCreator, idleTCPSeconds, connectionTimeoutTCPMillis, peerConnection); return futureResponse; } /** * Send a TCP message and don't expect a reply. * * @param channelCreator * The channel creator will create a TCP connection * @return The future that was added in the constructor */ public K fireAndForgetTCP(final ChannelCreator channelCreator) { connectionBean.sender().sendTCP(null, futureResponse, message, channelCreator, 0, connectionTimeoutTCPMillis, null); return futureResponse; } @Override public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception { LOG.debug("Error originating from: {}, cause {}", futureResponse.getRequest(), cause); if (futureResponse.isCompleted()) { LOG.warn("Got exception, but ignored (future response completed): {}", futureResponse.getFailedReason()); } else { if (LOG.isDebugEnabled()) { LOG.debug("exception caugth, but handled properly: " + cause.toString()); } if (cause instanceof PeerException) { PeerException pe = (PeerException) cause; if (pe.getAbortCause() != PeerException.AbortCause.USER_ABORT) { boolean force = pe.getAbortCause() != PeerException.AbortCause.TIMEOUT; // do not force if we ran into a timeout, the peer may be // busy boolean added = peerBean.peerMap().peerFailed(futureResponse.getRequest().getRecipient(), force); if (added) { if (LOG.isWarnEnabled()) { LOG.warn("removed from map, cause: " + pe.toString() + " msg: " + message); } } else if (LOG.isDebugEnabled()) { LOG.debug(pe.toString() + " msg: " + message); } } else if (LOG.isWarnEnabled()) { LOG.warn("error in request", cause); } } else { peerBean.peerMap().peerFailed(futureResponse.getRequest().getRecipient(), true); } } reportFailed(ctx.close(), cause); } @Override protected void channelRead0(final ChannelHandlerContext ctx, final Message2 responseMessage) throws Exception { MessageID recvMessageID = new MessageID(responseMessage); // Error handling if (responseMessage.getType() == Message2.Type.UNKNOWN_ID) { String msg = "Message was not delivered successfully: " + this.message; exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg)); return; } else if (responseMessage.getType() == Message2.Type.EXCEPTION) { String msg = "Message caused an exception on the other side, handle as peer_abort: " + this.message; exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg)); return; } else if (!sendMessageID.equals(recvMessageID)) { String msg = "Message [" + responseMessage + "] sent to the node is not the same as we expect. We sent [" + this.message + "]"; exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg)); return; } // We got a good answer, let's mark the sender as alive if (responseMessage.isOk() || responseMessage.isNotOk()) { peerBean.peerMap().peerFound(responseMessage.getSender(), null); } // call this for streaming support futureResponse.progress(responseMessage); if (!responseMessage.isDone()) { LOG.debug("message is streaming {}", responseMessage); return; } // Now we now we have the right message LOG.debug("perfect: {}", responseMessage); if (!message.isKeepAlive()) { reportMessage(ctx.close(), responseMessage); } else { futureResponse.setResponse(responseMessage); } } /** * Report a successful response after the channel was closed. * * @param close * The close future * @param responseMessage * The response message */ private void reportMessage(final ChannelFuture close, final Message2 responseMessage) { close.addListener(new GenericFutureListener<ChannelFuture>() { @Override public void operationComplete(final ChannelFuture arg0) throws Exception { LOG.debug("report success {}", responseMessage); futureResponse.setResponse(responseMessage); } }); } /** * Report a failure after the channel was closed. * * @param close * The close future * @param cause * The response message */ private void reportFailed(final ChannelFuture close, final Throwable cause) { close.addListener(new GenericFutureListener<ChannelFuture>() { @Override public void operationComplete(final ChannelFuture arg0) throws Exception { LOG.debug("report failure {}", cause); futureResponse.setFailed(cause); } }); } @Override public void channelInactive(final ChannelHandlerContext ctx) throws Exception { futureResponse.setFailed("channel is inactive"); } }