Apologies in advance if this is the incorrect mailing list.
We are trying to implement High Avaliablity with HttpClient (great package
btw), we dont have the luxury of a load balancer to do this for now, so we
(I) thought we could do it programmatically.
The way I thought would work, is to subclass the
SimpleHttpConnectionManager and return a connection that succeeded. Below
is the source for this implementation.
Everything seems to work fine, the failover succeeds and the client
connects to the failover host. However, under heavy usage, and if the
primary host is down, the occasional error occurs, with the exception:
Address already in use: connect
java.net.BindException: Address already in use: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:305)
at
java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:171)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:158)
at java.net.Socket.connect(Socket.java:452)
at java.net.Socket.connect(Socket.java:402)
at java.net.Socket.<init>(Socket.java:309)
at java.net.Socket.<init>(Socket.java:184)
at
org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:79)
at
org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:121)
at
org.apache.commons.httpclient.HttpConnection.open(HttpConnection.java:704)
at
org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:382)
at
org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:168)
at
org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:393)
at
org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:324)
at
com.jpmorgan.cove.api.http.OrderValidationImpl.validate(OrderValidationImpl.java:251)
at
com.jpmorgan.cove.api.http.OrderValidationImpl.validateInstruments(OrderValidationImpl.java:113)
at
com.jpmorgan.cove.api.http.OrderValidationImpl.validateInstrument(OrderValidationImpl.java:97)
at
com.jpmorgan.cove.api.test.ClientAPITest.testValidateInstrumentsAsNonBatch(ClientAPITest.java:142)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
This is the source, am I doing something wrong (or everything wrong??)
/**
* This inner class attempts to implement an HA version of the
SimpleHttpConnectionManager.
*/
protected final class HAHttpConnectionManager extends
SimpleHttpConnectionManager {
// set up the primary and failover host configurations
private URL primaryUrl;
private URL failoverUrl;
private HostConfiguration primary;
private HostConfiguration failover;
private String user;
private String pass;
// used to determaine which path to return based on the this flag
private boolean primaryIsOK;
private String path;
/**
* Constructor with a list of failover hosts.
* Note: this is a list of failover hosts only, the primary is
passed
* to the method to get the connection.
*
* @param failOverHosts
* @return HttpConnection if available or null if not
*/
public HAHttpConnectionManager() {
try {
primaryUrl = new
URL(ConfigHelper.getInstance().getProperty(
PrimaryURLKey));
failoverUrl = new
URL(ConfigHelper.getInstance().getProperty(
FailoverURLKey));
primary = new HostConfiguration();
failover = new HostConfiguration();
primary.setHost(primaryUrl.getHost(),
primaryUrl.getPort(),
primaryUrl.getProtocol());
failover.setHost(failoverUrl.getHost(),
failoverUrl.getPort(),
failoverUrl.getProtocol());
user = ConfigHelper.getInstance().getProperty(USERKey);
pass = ConfigHelper.getInstance().getProperty(PASSKey);
} catch (Exception e) {
_logger.warn("unable to instantiate "
+ this.getClass().getName(), e);
throw new RuntimeException("Error creating instance of "
+ this.getClass().getName());
}
}
/**
*
*/
public HttpConnection getConnection(HostConfiguration config) {
return getConnectionWithTimeout(config, 0L);
}
/**
*
*/
public HttpConnection getConnectionWithTimeout(
HostConfiguration primary, long timeout) {
HttpConnection connection = null;
// set the flag to assume primary connection is ok
primaryIsOK = true;
// let the sub class create a connection for us
connection = super.getConnectionWithTimeout(primary, timeout);
// test the connection for validity, if primary failed start
failover
if (!isValidConnection(connection)) {
// primary connection was bad, try the failover
connections
primaryIsOK = false;
_logger.warn("primary connection failed: "
+ primary.getHostURL());
if (null != failover) {
_logger.info("trying failover host "
+ failover.getHostURL());
connection = super.getConnectionWithTimeout(failover,
timeout);
// test if the connection is valid
if (isValidConnection(connection)) {
_logger.info("failover succeeded using host: "
+ failover.getHostURL());
} else {
_logger.warn("failover also failed to connect: "
+ failover.getHostURL());
}
}
}
return connection;
}
/**
*
* @param connection
* @return
*/
private boolean isValidConnection(HttpConnection connection) {
if (null == connection)
return false;
String uri = connection.getHost() + ":" +
connection.getPort();
try {
// try to open a connection
if (connection.isOpen()) {
_logger.debug("connection is open so is ok: " + uri);
return true;
}
_logger.debug("testing connection: " + uri);
connection.open();
// success, so close a connection
connection.close();
// return connection is ok
_logger.debug("connection is ok.");
return true;
} catch (IOException io) {
// connection open failed
_logger.warn("HttpConnection is bad: " + uri);
return false;
}
}
/**
* set the auth for the given connection.
* @param config
*/
public AuthScope getAuthScope() {
String host = null;
int port = 0;
// check which host we are using to authenticate
if (primaryIsOK) {
host = primary.getHost();
port = primary.getPort();
} else {
host = failover.getHost();
port = failover.getPort();
}
// use BASIC authentication for any realm
return new AuthScope(host, port, AuthScope.ANY_REALM,
AuthPolicy.BASIC);
}
public Credentials getCredentials() {
return new UsernamePasswordCredentials(user, pass);
}
/*
* get the path from the url to the servlet.
*/
public String getPath() {
return primaryIsOK == true ? primaryUrl.getPath() :
failoverUrl
.getPath();
}
/*
* get the primary config.
*/
public HostConfiguration getPrimaryHostConfiguration() {
return primary;
}
public HostConfiguration getFailoverHostConfiguration() {
return failover;
}
public HostConfiguration getHostConfiguration() {
return primaryIsOK == true ? primary : failover;
}
}
This communication is for informational purposes only. It is not intended
as an offer or solicitation for the purchase or sale of any financial
instrument or as an official confirmation of any transaction. All market prices,
data and other information are not warranted as to completeness or accuracy and
are subject to change without notice. Any comments or statements made herein
do not necessarily reflect those of JPMorgan Chase & Co., its subsidiaries
and affiliates