/*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.impl.endpoint.servlethttp;
import net.jxta.document.MimeMediaType;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.StringMessageElement;
import net.jxta.endpoint.WireFormatMessage;
import net.jxta.endpoint.WireFormatMessageFactory;
import net.jxta.impl.endpoint.BlockingMessenger;
import net.jxta.impl.endpoint.EndpointServiceImpl;
import net.jxta.impl.endpoint.transportMeter.TransportBindingMeter;
import net.jxta.impl.endpoint.transportMeter.TransportMeterBuildSettings;
import net.jxta.impl.util.TimeUtils;
import net.jxta.logging.Logging;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Simple messenger that simply posts a message to a URL.
*
* <p/>URL/HttpURLConnection is used, so (depending on your JDK) you will get
* reasonably good persistent connection management.
*/
final class HttpClientMessenger extends BlockingMessenger {
/**
* Logger.
*/
private final static transient Logger LOG = Logger.getLogger(HttpClientMessenger.class.getName());
/**
* Minimum amount of time between poll
*/
private final static int MIMIMUM_POLL_INTERVAL = (int) (5 * TimeUtils.ASECOND);
/**
* Amount of time to wait for connections to open.
*/
private final static int CONNECT_TIMEOUT = (int) (15 * TimeUtils.ASECOND);
/**
* Amount of time we are willing to wait for responses. This is the amount
* of time between our finishing sending a message or beginning a poll and
* the beginning of receipt of a response.
*/
private final static int RESPONSE_TIMEOUT = (int) (2 * TimeUtils.AMINUTE);
/**
* Amount of time we are willing to accept for additional responses. This
* is the total amount of time we are willing to wait after receiving an
* initial response message whether additional responses are sent or not.
* This setting governs the latency with which we switch back and forth
* between sending and receiving messages.
*/
private final static int EXTRA_RESPONSE_TIMEOUT = (int) (2 * TimeUtils.AMINUTE);
/**
* Messenger idle timeout.
*/
private final static long MESSENGER_IDLE_TIMEOUT = 15 * TimeUtils.AMINUTE;
/**
* Number of attempts we will attempt to make connections.
*/
private final static int CONNECT_RETRIES = 2;
/**
* Warn only once about obsolete proxies.
*/
private static boolean neverWarned = true;
/**
* The URL we send messages to.
*/
private final URL senderURL;
/**
* The ServletHttpTransport that created this object.
*/
private final ServletHttpTransport servletHttpTransport;
/**
* The Return Address element we will add to all messages we send.
*/
private final MessageElement srcAddressElement;
/**
* The logical destination address of this messenger.
*/
private final EndpointAddress logicalDest;
private TransportBindingMeter transportBindingMeter;
/**
* The last time at which we successfully received or sent a message.
*/
private transient long lastUsed = TimeUtils.timeNow();
/**
* Poller that we use to get our messages.
*/
private MessagePoller poller = null;
/**
* Constructs the messenger.
*
* @param servletHttpTransport The transport this messenger will work for.
* @param srcAddr The source address.
* @param destAddr The destination address.
*/
HttpClientMessenger(ServletHttpTransport servletHttpTransport, EndpointAddress srcAddr, EndpointAddress destAddr) throws IOException {
// We do use self destruction.
super(servletHttpTransport.getEndpointService().getGroup().getPeerGroupID(), destAddr, true);
this.servletHttpTransport = servletHttpTransport;
EndpointAddress srcAddress = srcAddr;
this.srcAddressElement = new StringMessageElement(EndpointServiceImpl.MESSAGE_SOURCE_NAME, srcAddr.toString(), null);
String protoAddr = destAddr.getProtocolAddress();
String host;
int port;
int lastColon = protoAddr.lastIndexOf(':');
if ((-1 == lastColon) || (lastColon < protoAddr.lastIndexOf(']')) || ((lastColon + 1) == protoAddr.length())) {
// There's no port or it's an IPv6 addr with no port or the colon is the last character.
host = protoAddr;
port = 80;
} else {
host = protoAddr.substring(0, lastColon);
port = Integer.parseInt(protoAddr.substring(lastColon + 1));
}
senderURL = new URL("http", host, port, "/");
logicalDest = retreiveLogicalDestinationAddress();
// Start receiving messages from the other peer
poller = new MessagePoller(srcAddr.getProtocolAddress(), destAddr);
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("New messenger : " + this );
}
}
/*
* The cost of just having a finalize routine is high. The finalizer is
* a bottleneck and can delay garbage collection all the way to heap
* exhaustion. Leave this comment as a reminder to future maintainers.
* Below is the reason why finalize is not needed here.
*
* These messengers (normally) never go to the application layer. Endpoint
* code does call close when necessary.
protected void finalize() {
}
*/
/**
* {@inheritDoc}
* <p/>
* A simple implementation for debugging. <b>Do not parse the String
* returned. All of the information is available in other (simpler) ways.</b>
*/
public String toString() {
StringBuilder result = new StringBuilder(super.toString());
result.append(" {");
result.append(getDestinationAddress());
result.append(" / ");
result.append(getLogicalDestinationAddress());
result.append("}");
return result.toString();
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void closeImpl() {
if (isClosed()) {
return;
}
super.close();
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Close messenger to " + senderURL);
}
MessagePoller stopPoller = poller;
poller = null;
if (null != stopPoller) {
stopPoller.stop();
}
}
/**
* {@inheritDoc}
*/
@Override
public void sendMessageBImpl(Message message, String service, String serviceParam) throws IOException {
if (isClosed()) {
IOException failure = new IOException("Messenger was closed, it cannot be used to send messages.");
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Messenger was closed, it cannot be used to send messages.", failure);
}
throw failure;
}
// clone the message before modifying it.
message = message.clone();
// Set the message with the appropriate src and dest address
message.replaceMessageElement(EndpointServiceImpl.MESSAGE_SOURCE_NS, srcAddressElement);
EndpointAddress destAddressToUse = getDestAddressToUse(service, serviceParam);
MessageElement dstAddressElement = new StringMessageElement(EndpointServiceImpl.MESSAGE_DESTINATION_NAME,
destAddressToUse.toString(), null);
message.replaceMessageElement(EndpointServiceImpl.MESSAGE_DESTINATION_NS, dstAddressElement);
try {
doSend(message);
} catch (IOException e) {
// close this messenger
close();
// rethrow the exception
throw e;
}
}
/**
* {@inheritDoc}
*/
@Override
public EndpointAddress getLogicalDestinationImpl() {
return logicalDest;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isIdleImpl() {
return isClosed() || (TimeUtils.toRelativeTimeMillis(TimeUtils.timeNow(), lastUsed) > MESSENGER_IDLE_TIMEOUT);
}
/**
* Connects to the http server and retrieves the Logical Destination Address
*/
private EndpointAddress retreiveLogicalDestinationAddress() throws IOException {
long beginConnectTime = 0;
long connectTime = 0;
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Ping (" + senderURL + ")");
}
if (TransportMeterBuildSettings.TRANSPORT_METERING) {
beginConnectTime = TimeUtils.timeNow();
}
// open a connection to the other end
HttpURLConnection urlConn = (HttpURLConnection) senderURL.openConnection();
urlConn.setRequestMethod("GET");
urlConn.setDoOutput(true);
urlConn.setDoInput(true);
urlConn.setAllowUserInteraction(false);
urlConn.setUseCaches(false);
urlConn.setConnectTimeout(CONNECT_TIMEOUT);
urlConn.setReadTimeout(CONNECT_TIMEOUT);
try {
// this is where the connection is actually made, if not already
// connected. If we can't connect, assume it is dead
int code = urlConn.getResponseCode();
if (code != HttpURLConnection.HTTP_OK) {
if (TransportMeterBuildSettings.TRANSPORT_METERING) {
transportBindingMeter = servletHttpTransport.getTransportBindingMeter(null, getDestinationAddress());
if (transportBindingMeter != null) {
transportBindingMeter.connectionFailed(true, TimeUtils.timeNow() - beginConnectTime);
}
}
throw new IOException("Message not accepted: HTTP status " + "code=" + code + " reason=" + urlConn.getResponseMessage());
}
// check for a returned peerId
int msglength = urlConn.getContentLength();
if (msglength <= 0) {
throw new IOException("Ping response was empty.");
}
InputStream inputStream = urlConn.getInputStream();
// read the peerId
byte[] uniqueIdBytes = new byte[msglength];
int bytesRead = 0;
while (bytesRead < msglength) {
int thisRead = inputStream.read(uniqueIdBytes, bytesRead, msglength - bytesRead);
if (thisRead < 0) {
break;
}
bytesRead += thisRead;
}
if (bytesRead < msglength) {
throw new IOException("Content ended before promised Content length");
}
String uniqueIdString;
try {
uniqueIdString = new String(uniqueIdBytes, "UTF-8");
} catch (UnsupportedEncodingException never) {
// utf-8 is always available, but we handle it anyway.
uniqueIdString = new String(uniqueIdBytes);
}
if (TransportMeterBuildSettings.TRANSPORT_METERING) {
connectTime = TimeUtils.timeNow();
transportBindingMeter = servletHttpTransport.getTransportBindingMeter(uniqueIdString, getDestinationAddress());
if (transportBindingMeter != null) {
transportBindingMeter.connectionEstablished(true, connectTime - beginConnectTime);
transportBindingMeter.ping(connectTime);
transportBindingMeter.connectionClosed(true, connectTime - beginConnectTime);
}
}
EndpointAddress remoteAddress = new EndpointAddress("jxta", uniqueIdString.trim(), null, null);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Ping (" + senderURL + ") -> " + remoteAddress);
}
return remoteAddress;
} catch (IOException failure) {
if (TransportMeterBuildSettings.TRANSPORT_METERING) {
connectTime = TimeUtils.timeNow();
transportBindingMeter = servletHttpTransport.getTransportBindingMeter(null, getDestinationAddress());
if (transportBindingMeter != null) {
transportBindingMeter.connectionFailed(true, connectTime - beginConnectTime);
}
}
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Ping (" + senderURL + ") -> failed");
}
throw failure;
}
}
/**
* Connects to the http server and POSTs the message
*/
private void doSend(Message msg) throws IOException {
long beginConnectTime = 0;
long connectTime = 0;
if (TransportMeterBuildSettings.TRANSPORT_METERING) {
beginConnectTime = TimeUtils.timeNow();
}
WireFormatMessage serialed = WireFormatMessageFactory.toWire(msg, EndpointServiceImpl.DEFAULT_MESSAGE_TYPE, null);
for (int connectAttempt = 1; connectAttempt <= CONNECT_RETRIES; connectAttempt++) {
if (connectAttempt > 1) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Retrying connection to " + senderURL);
}
}
// open a connection to the other end
HttpURLConnection urlConn = (HttpURLConnection) senderURL.openConnection();
try {
urlConn.setRequestMethod("POST");
urlConn.setDoOutput(true);
urlConn.setDoInput(true);
urlConn.setAllowUserInteraction(false);
urlConn.setUseCaches(false);
urlConn.setConnectTimeout(CONNECT_TIMEOUT);
urlConn.setReadTimeout(CONNECT_TIMEOUT);
// FIXME 20040907 bondolo Should set message encoding http header.
urlConn.setRequestProperty("content-length", Long.toString(serialed.getByteLength()));
urlConn.setRequestProperty("content-type", serialed.getMimeType().toString());
// send the message
OutputStream out = urlConn.getOutputStream();
if (TransportMeterBuildSettings.TRANSPORT_METERING && (transportBindingMeter != null)) {
connectTime = TimeUtils.timeNow();
transportBindingMeter.connectionEstablished(true, connectTime - beginConnectTime);
}
serialed.sendToStream(out);
out.flush();
int responseCode;
try {
responseCode = urlConn.getResponseCode();
} catch (SocketTimeoutException expired) {
// maybe a retry will help.
continue;
} catch (IOException ioe) {
// Could not connect. This seems to happen a lot with a loaded HTTP 1.0
// proxy. Apparently, HttpUrlConnection can be fooled by the proxy
// in believing that the connection is still open and thus breaks
// when attempting to make a second transaction. We should not have to but it
// seems that it befalls us to retry.
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("HTTP 1.0 proxy seems in use");
}
// maybe a retry will help.
continue;
}
// NOTE: If the proxy closed the connection 1.0 style without returning
// a status line, we do not get an exception: we get a -1 response code.
// Apparently, proxies no-longer do that anymore. Just in case, we issue a
// warning and treat it as OK.71
if (responseCode == -1) {
if (neverWarned && Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Obsolete HTTP proxy does not issue HTTP_OK response. Assuming OK");
neverWarned = false;
}
responseCode = HttpURLConnection.HTTP_OK;
}
if (responseCode != HttpURLConnection.HTTP_OK) {
if (TransportMeterBuildSettings.TRANSPORT_METERING && (transportBindingMeter != null)) {
transportBindingMeter.dataSent(true, serialed.getByteLength());
transportBindingMeter.connectionDropped(true, TimeUtils.timeNow() - beginConnectTime);
}
throw new IOException( "Message not accepted: HTTP status " + "code=" + responseCode +
" reason=" + urlConn.getResponseMessage());
}
if (TransportMeterBuildSettings.TRANSPORT_METERING && (transportBindingMeter != null)) {
long messageSentTime = TimeUtils.timeNow();
transportBindingMeter.messageSent(true, msg, messageSentTime - connectTime, serialed.getByteLength());
transportBindingMeter.connectionClosed(true, messageSentTime - beginConnectTime);
}
// note that we successfully sent a message
lastUsed = TimeUtils.timeNow();
return;
} finally {
// This does prevent the creation of an infinite number of connections
// if we happen to be going through a 1.0-only proxy or connect to a server
// that still does not set content length to zero for the response. With this, at
// least we close them (they eventualy close anyway because the other side closes
// them but it takes too much time). If content-length is set, then jdk ignores
// the disconnect AND reuses the connection, which is what we want.
urlConn.disconnect();
}
}
throw new IOException("Failed sending " + msg + " to " + senderURL);
}
/**
* Polls for messages sent to us.
*/
private class MessagePoller implements Runnable {
/**
* If <tt>true</tt> then this poller is stopped or stopping.
*/
private volatile boolean stopped = false;
/**
* The thread that does the work.
*/
private Thread pollerThread;
/**
* The URL we poll for messages.
*/
private final URL pollingURL;
MessagePoller(String pollAddress, EndpointAddress destAddr) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("new MessagePoller for " + senderURL);
}
/*
* query string is of the format ?{response timeout},{extra response timeout},{dest address}
*
* The timeout's are expressed in milliseconds. -1 means do not wait
* at all, 0 means wait forever.
*/
try {
pollingURL = new URL(senderURL,
"/" + pollAddress +
"?" + Integer.toString(RESPONSE_TIMEOUT) + "," +
Integer.toString(EXTRA_RESPONSE_TIMEOUT) + "," +
destAddr);
} catch (MalformedURLException badAddr) {
IllegalArgumentException failure = new IllegalArgumentException("Could not construct polling URL");
failure.initCause(badAddr);
throw failure;
}
pollerThread = new Thread(this, "HttpClientMessenger poller for " + senderURL);
pollerThread.setDaemon(true);
pollerThread.start();
}
protected void stop() {
if (stopped) {
return;
}
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("Stop polling for " + senderURL);
}
stopped = true;
// Here, we are forced to abandon this object open. Because we could
// get blocked forever trying to close it. It will rot away after
// the current read returns. The best we can do is interrupt the
// thread; unlikely to have an effect per the current.
// HttpURLConnection implementation.
Thread stopPoller = pollerThread;
if (null != stopPoller) {
stopPoller.interrupt();
}
}
/**
* Returns {@code true} if this messenger is stopped otherwise
* {@code false}.
*
* @return returns {@code true} if this messenger is stopped otherwise
* {@code false}.
*/
protected boolean isStopped() {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(this + " " + senderURL + " --> " + (stopped ? "stopped" : "running"));
}
return stopped;
}
/**
* {@inheritDoc}
*
* <p/>Connects to the http server and waits for messages to be received and processes them.
*/
public void run() {
try {
long beginConnectTime = 0;
long connectTime = 0;
long noReconnectBefore = 0;
HttpURLConnection conn = null;
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("Message polling beings for " + pollingURL);
}
int connectAttempt = 1;
// get messages until the messenger is closed
while (!isStopped()) {
if (conn == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Opening new connection to " + pollingURL);
}
conn = (HttpURLConnection) pollingURL.openConnection(); // Incomming data channel
conn.setRequestMethod("GET");
conn.setDoOutput(false);
conn.setDoInput(true);
conn.setAllowUserInteraction(false);
conn.setUseCaches(false);
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(RESPONSE_TIMEOUT);
if (TransportMeterBuildSettings.TRANSPORT_METERING) {
beginConnectTime = TimeUtils.timeNow();
}
// Loop back and try again to connect
continue;
}
long untilNextConnect = TimeUtils.toRelativeTimeMillis(noReconnectBefore);
try {
if (untilNextConnect > 0) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Delaying for " + untilNextConnect + "ms before reconnect to " + senderURL);
}
Thread.sleep(untilNextConnect);
}
} catch (InterruptedException woken) {
Thread.interrupted();
continue;
}
InputStream inputStream;
MimeMediaType messageType;
try {
if (connectAttempt > 1) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Reconnect attempt for " + senderURL);
}
}
// Always connect (no cost if connected).
conn.connect();
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Waiting for response code from " + senderURL);
}
int responseCode = conn.getResponseCode();
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(
"Response " + responseCode + " for Connection : " + senderURL + "\n\tContent-Type : "
+ conn.getHeaderField("Content-Type") + "\tContent-Length : "
+ conn.getHeaderField("Content-Length") + "\tTransfer-Encoding : "
+ conn.getHeaderField("Transfer-Encoding"));
}
connectTime = TimeUtils.timeNow();
noReconnectBefore = TimeUtils.toAbsoluteTimeMillis(MIMIMUM_POLL_INTERVAL, connectTime);
if (0 == conn.getContentLength()) {
conn.disconnect();
conn = null;
continue;
}
if (HttpURLConnection.HTTP_NO_CONTENT == responseCode) {
// the connection timed out.
if (TransportMeterBuildSettings.TRANSPORT_METERING && (transportBindingMeter != null)) {
transportBindingMeter.connectionClosed(true, TimeUtils.toRelativeTimeMillis(beginConnectTime, connectTime));
}
conn = null;
continue;
}
if (responseCode != HttpURLConnection.HTTP_OK) {
if (TransportMeterBuildSettings.TRANSPORT_METERING && (transportBindingMeter != null)) {
transportBindingMeter.connectionClosed(true, TimeUtils.timeNow() - beginConnectTime);
}
throw new IOException("HTTP Failure: " + conn.getResponseCode() + " : " + conn.getResponseMessage());
}
String contentType = conn.getHeaderField("Content-Type");
if (null == contentType) {
// XXX 20051219 bondolo Figure out why the mime type is not always set.
messageType = EndpointServiceImpl.DEFAULT_MESSAGE_TYPE;
} else {
messageType = MimeMediaType.valueOf(contentType);
}
// FIXME 20040907 bondolo Should get message content-encoding from http header.
inputStream = conn.getInputStream();
// reset connection attempt.
connectAttempt = 1;
} catch (InterruptedIOException broken) {
// We don't know where it was interrupted. Restart connection.
Thread.interrupted();
if (connectAttempt > CONNECT_RETRIES) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning("Unable to connect to " + senderURL);
}
stop();
break;
} else {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Failed connecting to " + senderURL);
}
if (null != conn) {
conn.disconnect();
}
conn = null;
connectAttempt++;
continue;
}
} catch (IOException ioe) {
if (connectAttempt > CONNECT_RETRIES) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Unable to connect to " + senderURL, ioe);
}
stop();
break;
} else {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Failed connecting to " + senderURL);
}
if (null != conn) {
conn.disconnect();
}
conn = null;
connectAttempt++;
continue;
}
}
// start receiving messages
try {
while (!isStopped()
&& (TimeUtils.toRelativeTimeMillis(TimeUtils.timeNow(), connectTime) < RESPONSE_TIMEOUT)) {
// read a message!
long messageReceiveStart = TimeUtils.timeNow();
Message incomingMsg;
incomingMsg = WireFormatMessageFactory.fromWire(inputStream, messageType, null);
if (TransportMeterBuildSettings.TRANSPORT_METERING && (transportBindingMeter != null)) {
transportBindingMeter.messageReceived(true, incomingMsg, incomingMsg.getByteLength(),
TimeUtils.timeNow() - messageReceiveStart);
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Received " + incomingMsg + " from " + senderURL);
}
servletHttpTransport.executor.execute(new MessageProcessor(incomingMsg));
// note that we received a message
lastUsed = TimeUtils.timeNow();
}
if (TransportMeterBuildSettings.TRANSPORT_METERING && (transportBindingMeter != null)) {
transportBindingMeter.connectionClosed(true, TimeUtils.timeNow() - beginConnectTime);
}
} catch (EOFException e) {
// Connection ran out of messages. let it go.
conn = null;
} catch (InterruptedIOException broken) {
if (TransportMeterBuildSettings.TRANSPORT_METERING && (transportBindingMeter != null)) {
transportBindingMeter.connectionDropped(true, TimeUtils.timeNow() - beginConnectTime);
}
// We don't know where it was interrupted. Restart connection.
Thread.interrupted();
if (null != conn) {
conn.disconnect();
}
conn = null;
} catch (IOException e) {
if (TransportMeterBuildSettings.TRANSPORT_METERING && (transportBindingMeter != null)) {
transportBindingMeter.connectionDropped(true, TimeUtils.timeNow() - beginConnectTime);
}
// If we managed to get down here, it is really an error.
// However, being disconnected from the server, for
// whatever reason, is a common place event. No need to
// clutter the screen with scary messages. When the
// message layer believes it's serious, it prints the
// scary message already.
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Failed to read message from " + senderURL, e);
}
if (null != conn) {
conn.disconnect();
}
conn = null;
} finally {
try {
inputStream.close();
} catch (IOException ignored) {
//ignored
}
}
}
} catch (Throwable argh) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE, "Poller exiting because of uncaught exception", argh);
}
stop();
} finally {
pollerThread = null;
}
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("Message polling stopped for " + senderURL);
}
}
}
/**
* A small class for processing individual messages.
*/
private class MessageProcessor implements Runnable {
private Message msg;
MessageProcessor(Message msg) {
this.msg = msg;
}
public void run() {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Demuxing " + msg + " from " + senderURL);
}
servletHttpTransport.getEndpointService().processIncomingMessage(msg);
}
}
}