package com.limegroup.gnutella.http;
import java.io.IOException;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.auth.HttpAuthenticator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A simple manager class that maintains a single HttpConnectionManager
* and doles out either a simple one (for Java 1.1.8) or the MultiThreaded
* one (for all other versions)
*/
public class HttpClientManager {
private static final Log LOG = LogFactory.getLog(HttpClientManager.class);
/**
* The amount of time to wait while trying to connect to a specified
* host via TCP. If we exceed this value, an IOException is thrown
* while trying to connect.
*/
private static final int CONNECTION_TIMEOUT = 5000;
/**
* The amount of time to wait while receiving data from a specified
* host. Used as an SO_TIMEOUT.
*/
private static final int TIMEOUT = 8000;
/**
* The maximum number of times to allow redirects from hosts.
*/
private static final int MAXIMUM_REDIRECTS = 10;
/**
* The time to allow a connection to sit idle, waiting for something
* to reuse it.
*/
private static final long IDLE_TIME = 30 * 1000; // 30 seconds.
/**
* The manager which all client connections use if not Java 1.1.8;
*/
private static final HttpConnectionManager MANAGER;
static {
MANAGER = new MultiThreadedHttpConnectionManager();
((MultiThreadedHttpConnectionManager)MANAGER).
setIdleConnectionTime(IDLE_TIME);
}
/**
* Returns a new HttpClient with the appropriate manager.
*/
public static HttpClient getNewClient() {
return getNewClient(CONNECTION_TIMEOUT, TIMEOUT);
}
/**
* Returns a new HttpClient with the appropriate manager and parameters.
*
* @param connectTimeout the number of milliseconds to wait to establish
* a TCP connection with the remote host
* @param soTimeout the socket timeout -- the number of milliseconds to
* wait for data before closing an established socket
*/
public static HttpClient getNewClient(int connectTimeout, int soTimeout) {
HttpClient client = new HttpClient(MANAGER);
client.setConnectionTimeout(connectTimeout);
client.setTimeout(soTimeout);
return client;
}
/**
* Executes the given HttpMethod in the HttpClient, following redirects.
* This method is needed because HttpClient does not support redirects
* across protocols, hosts, and/or ports.
*/
public static void executeMethodRedirecting(HttpClient client,
HttpMethod methid)
throws IOException, HttpException {
executeMethodRedirecting(client, methid, MAXIMUM_REDIRECTS);
}
/**
* Executes the given HttpMethod in the HttpClient, following redirecits
* up to the specific number of times.
* This method is needed because HttpClient does not support redirects
* across protocols, hosts, and/or ports.
*/
public static void executeMethodRedirecting(HttpClient client,
HttpMethod methid,
int redirects)
throws IOException, HttpException {
for(int i = 0; i < redirects; i++) {
if(LOG.isInfoEnabled())
LOG.info("Attempting connection (" + i + ") to " +
methid.getURI().getEscapedURI());
client.executeMethod(methid);
switch(methid.getStatusCode()) {
case HttpStatus.SC_MOVED_TEMPORARILY:
case HttpStatus.SC_MOVED_PERMANENTLY:
case HttpStatus.SC_SEE_OTHER:
case HttpStatus.SC_TEMPORARY_REDIRECT:
if(!methid.getFollowRedirects()) {
if(LOG.isInfoEnabled())
LOG.warn("Redirect requested but not supported");
throw new HttpException("Redirect requested");
}
Header locationHeader = methid.getResponseHeader("location");
if(locationHeader == null) {
if(LOG.isInfoEnabled())
LOG.warn("Redirect requested, no location header");
throw new HttpException("Redirected without a location");
}
String location = locationHeader.getValue();
if(LOG.isInfoEnabled())
LOG.info("Redirected requested to: " + location);
URI newLocation = new URI(location.toCharArray());
// Retrieve the RequestHeaders
Header[] requestHeaders = methid.getRequestHeaders();
// Recycle this method so we can use it again.
methid.recycle();
HostConfiguration hc = methid.getHostConfiguration();
hc.setHost(
newLocation.getHost(),
newLocation.getPort(),
newLocation.getScheme()
);
methid.setFollowRedirects(true);
for(int j = 0; j < requestHeaders.length; j++) {
if(!requestHeaders[j].getName().equals("Host"))
methid.addRequestHeader(requestHeaders[j]);
}
// Set up the new values for the method.
methid.setPath(newLocation.getEscapedPath());
methid.setQueryString(newLocation.getEscapedQuery());
methid.removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP);
// Loop around and try the method again.
break;
default:
return;
}
}
throw new HttpException("Maximum redirects encountered, bailing");
}
}