I'm working on implementing a custom TCP proxy server which acts like a load balancer. The proxy server will accept client requests and then forward them to available hosts.
I am concerned about the way that I am creating new client sockets between proxy server and host per request in the receiveHostResponse
method and that if it throws a connection refused exception then I retry with another host. Is it okay to create new client sockets between proxy and hosts per request or should I create them once per process? I think there would be a problem with concurrency if I tried once-per-process.
What are some best practises here?
public class ProxyServer extends Thread {
final static Logger logger = Logger.getLogger(ProxyServer.class);
private ServerSocket serverSocket;
private static ArrayList<HostConnections> hostList;
private int proxyServerPort;
public ProxyServer( ) throws IOException {
initialize();
}
/**
* This method will initialize all the prerequisites
* @throws IOException
*/
private void initialize() throws IOException {
PropertyConfigurator.configure("log4j.properties");
logger.info("Successfully configured log4j. ");
logger.info("************************* Started Initializing Proxy Server *************************");
int port = getProxyServerPort();
serverSocket = new ServerSocket(port);
logger.info("Started Proxy Server on Port " + port);
loadHostConfigurations();
logger.info(
"************************* Successfully initialized Proxy Server **********************");
}
/**
* This is the runner which takes the socket server requests
*/
public void run() {
while (true) {
try {
logger.info("Waiting for client on port " + serverSocket.getLocalPort() + "...");
Socket server = serverSocket.accept();
logger.info("Just connected to " + server.getRemoteSocketAddress());
DataInputStream in = new DataInputStream(server.getInputStream());
///receive client request message ()
String requestMessage = in.readUTF();
logger.info(requestMessage);
/////////////////////////////////////////////////////////////////////////////////////////////
// Chose available host by round robin
HostResponse hostResponse = null;
boolean reTry=false;
while(!reTry) {
HostConnections availableHost = getAvailableHostRoundRobin();
// send host request X
try {
hostResponse = null;
hostResponse = recieveHostResponse(availableHost, requestMessage);
reTry=true;
} catch (CCRuntimeException e) {
logger.error("############" + e.getMessage());
reTry=false;
}
}
//////////////////////////////////////////////////////////////////////
DataOutputStream out = new DataOutputStream(server.getOutputStream());
// send recieved response from host X to client
if(hostResponse!=null) {
out.writeUTF(hostResponse.getMessage());
}
out.flush();
out.close();
in.close();
server.close();
logger.info("Server proxy.isClosed() " + server.isClosed());
} catch (SocketTimeoutException s) {
logger.error("############Socket timed out!" + s.getMessage() + "Cause " + s.getCause());
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* This method will return the current available select by round robin way
*
* @return
*/
private HostConnections getAvailableHostRoundRobin() {
HostConnections availableHost = hostList.get(0);
//availableHost.isMarked();//==false;
Collections.rotate(hostList, -1);
logger.info("Found an Available host " + hostList.get(0));
return availableHost;
}
public static void main(String[] args) {
//int port = 2222;
try {
Thread t = new ProxyServer();
t.start();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* This method will send client message to the selected host and will return the
* host response
*
* @param availableHost
* @param request
* @return
* @throws CCRuntimeException
*/
public static HostResponse recieveHostResponse(HostConnections availableHost, String request)
throws CCRuntimeException {
logger.info("Invoked recieveHostResponse () : " + availableHost.toString());
HostResponse hostResponse = new HostResponse();
String serverName = availableHost.getHostIpAddress();
int port = availableHost.getHostPort();
try {
logger.info("Connecting to " + serverName + " on port " + port);
Socket client = new Socket(serverName, port);
logger.info("Just connected to " + client.getRemoteSocketAddress());
OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
logger.info("Sent Host Request " + request);
out.writeUTF(request);
out.flush();
InputStream inFromServer = client.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
hostResponse.setMessage(in.readUTF());
logger.info("Recieved host response " + hostResponse.getMessage());
out.close();
client.close();
hostResponse.setSuccess(true);
} catch (IOException e) {
hostResponse.setSuccess(false);
logger.error("############" + e.getMessage());
throw new CCRuntimeException(" Host is not reachable :" + availableHost.toString());
}
return hostResponse;
}
/**
* This method will load all the host configurations which are included in conf.properties file
*
* host configurations has to be configured as
* host_ipaddress1=192.168.60.65
* host_port1=7881
* host_ipaddress2=192.168.60.65
* host_port2=7882
*
* new host ip and host ports has to appended to the above pattern meaning host_ipaddress+{++1} host_port+[++1]
*
* */
private static void loadHostConfigurations() {
logger.info("Invoked loadHostConfigurations () method . ");
hostList = new ArrayList<HostConnections>();
int hostCounter = 1;
boolean hasHosts = true;
while (hasHosts) {
String hostIp = DataProperties.getInstance().getValue("host_ipaddress" + hostCounter);
String hostPort = DataProperties.getInstance().getValue("host_port" + hostCounter);
if (hostIp != null && hostPort != null) {
hostList.add(new HostConnections(hostIp, Integer.valueOf(hostPort)));
logger.info(" Loaded Host " + hostIp + " Port " + hostPort);
hostCounter++;
} else {
hasHosts = false;
}
}
logger.info("Number of host connections Loaded : " + (hostCounter-1));
logger.info("Loaded Host Connections " + hostList.toString());
}
/**
*
* This method will return proxy server port which is configured on conf.properties file
* if the port is not configured on the property file then this will return the default port 4444
* @return
*/
public int getProxyServerPort() {
String sPort=DataProperties.getInstance().getValue(Constants.SERVER_PORT_KEY);
if(sPort!=null){
return Integer.valueOf(sPort);
}
return Constants.DEFAULT_SERVER_PORT;
}
}