Java tutorial
/* * 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.connection; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import net.tomp2p.futures.FutureResponse; import net.tomp2p.message.Message; import net.tomp2p.message.MessageID; import net.tomp2p.peers.PeerAddress; import net.tomp2p.peers.PeerStatusListener; import net.tomp2p.rpc.RPC; 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<Message> { 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 Message 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; private final int slowResponseTimeoutSeconds; // = ConnectionBean.DEFAULT_SLOW_RESPONSE_TIMEOUT_SECONDS; /** * 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.request(); this.sendMessageID = new MessageID(message); this.idleTCPSeconds = configuration.idleTCPSeconds(); this.idleUDPSeconds = configuration.idleUDPSeconds(); this.connectionTimeoutTCPMillis = configuration.connectionTimeoutTCPMillis(); this.slowResponseTimeoutSeconds = configuration.slowResponseTimeoutSeconds(); } /** * @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; } /** * @return The time when a slow response time outs */ public int slowResponseTimeoutSeconds() { return slowResponseTimeoutSeconds; } /** * 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; } /** * Broadcast a UDP message (layer 2) and do not expect a reply. * * @param channelCreator * The channel creator will create a UDP connection * @return The future that was added in the constructor */ public K fireAndForgetBroadcastUDP(final ChannelCreator channelCreator) { connectionBean.sender().sendUDP(null, futureResponse, message, channelCreator, 0, 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; } public K sendTCP(final PeerConnection peerConnection) { connectionBean.sender().sendTCP(this, futureResponse, message, null, idleTCPSeconds, connectionTimeoutTCPMillis, peerConnection); 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; } @Override public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception { LOG.debug("Error originating from: {}, cause {}", futureResponse.request(), cause); if (futureResponse.isCompleted()) { LOG.warn("Got exception, but ignored (future response completed): {}", futureResponse.failedReason()); } else { if (LOG.isDebugEnabled()) { LOG.debug("exception caugth, but handled properly: " + cause.toString()); } if (cause instanceof PeerException) { PeerException pe = (PeerException) cause; if (pe.abortCause() != PeerException.AbortCause.USER_ABORT) { // do not force if we ran into a timeout, the peer may be // busy synchronized (peerBean.peerStatusListeners()) { for (PeerStatusListener peerStatusListener : peerBean.peerStatusListeners()) { peerStatusListener.peerFailed(futureResponse.request().recipient(), pe); } } LOG.debug("removed from map, cause: {} msg: {}", pe.toString(), message); } else { LOG.warn("error in request", cause); } } else { synchronized (peerBean.peerStatusListeners()) { for (PeerStatusListener peerStatusListener : peerBean.peerStatusListeners()) { peerStatusListener.peerFailed(futureResponse.request().recipient(), new PeerException(cause)); } } } } LOG.debug("report failure", cause); futureResponse.failedLater(cause); ctx.close(); } @Override protected void channelRead0(final ChannelHandlerContext ctx, final Message responseMessage) throws Exception { MessageID recvMessageID = new MessageID(responseMessage); // Error handling if (responseMessage.type() == Message.Type.UNKNOWN_ID) { String msg = "Message was not delivered successfully, unknow id (peer may be offline or unknown RPC handler): " + this.message; exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg)); return; } else if (responseMessage.type() == Message.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 (responseMessage.isRequest()) { ctx.fireChannelRead(responseMessage); 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; } else if (responseMessage.command() != RPC.Commands.RCON.getNr() && message.recipient().isRelayed() != responseMessage.sender().isRelayed()) { String msg = "Message [" + responseMessage + "] sent has a different relay flag than we sent [" + this.message + "]. Recipient (" + message.recipient().isRelayed() + ") / Sender (" + responseMessage.sender().isRelayed() + ")"; exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg)); return; } //NAT reflection, reverse lookup PeerAddress realAddress = peerBean.localMap().translateReverse(responseMessage.sender()); if (realAddress != null) { responseMessage.sender(realAddress); } // Stop time measurement of RTT futureResponse.stopRTTMeasurement(); // We got a good answer, let's mark the sender as alive //if its an announce, the peer status will be handled in the RPC if (responseMessage.command() != RPC.Commands.LOCAL_ANNOUNCE.getNr() && (responseMessage.isOk() || responseMessage.isNotOk())) { peerBean.notifyPeerFound(responseMessage.sender(), null, null, futureResponse.getRoundTripTime()); } // call this for streaming support if (!responseMessage.isDone()) { LOG.debug("good message is streaming {}", responseMessage); return; } // support slow, unreachable devices which cannot respond instantly if (this.message.recipient().isRelayed() && this.message.recipient().isSlow() && responseMessage.type() == Message.Type.PARTIALLY_OK) { LOG.debug("Received partially ok by the relay peer. Wait for answer of the unreachable peer."); // wait for the (real) answer of the unreachable peer. connectionBean.dispatcher().addPendingRequest(message.messageId(), futureResponse, slowResponseTimeoutSeconds, connectionBean.timer()); // close the channel to the relay peer ctx.close(); return; } if (!message.isKeepAlive()) { LOG.debug("good message, we can close {}, {}", responseMessage, ctx.channel()); //set the success now, but trigger the notify when we closed the channel. futureResponse.responseLater(responseMessage); //the channel creater adds a listener that sets futureResponse.setResponseNow, when the channel is closed ctx.close(); } else { LOG.debug("good message, leave open {}", responseMessage); futureResponse.response(responseMessage); } } }