Written in 1999 for CS-352 Internet Technologies at Rutgers University.
A proxy server is basically an intermediary forwarding station between two computers, a client and a server.
The way that it works is fairly simple and straightforward. The proxy server sits and waits, listening to a particular port on the local machine where it is running. Once a request is made to that port, the proxy then establishes a connection to a specified port on a specified remote host.
Any data transfered from the client to the proxy is forwarded from the proxy to the remote host. Likewise, any responses recieved from the remote host by the proxy are forwarded to the client. In this way, the proxy server acts as a "middle man" between the client and the remote host. It recieves all the data from each, and forwards it to the other.
Both the client and the remote host have no idea that the proxy is between them, as it is invisible to each. They send their requests and responses, and the proxy takes care of the connections between both.
As such, the proxy must act as both a client and a server. It is a server to the original client requesting data from the remote host. The proxy intercepts this request as if it were the server, and then sends this same request to the remote host, acting as if it were a client. When the remote host sends it's response, the proxy recieves it as a client, and then send it to the client as if it were the server.
The stand-alone Java proxy server proxy called "proxyserver.java", will correctly do the following:
-
Read the local port, remote machine name and remote port as command line arguments (in that order).
-
Create a socket and accept TCP requests on the local port.
-
Create a socket and connect to the remote machine's remote TCP port.
-
Create two threads, one to handle each of the following:
-
Read an unknown amount data from the local port and forward it to the remote port
-
Read an unknown amount of data from the remote port and forward it to the local port.
-
Close the sockets if either closes or an exception occurs.
-
Correctly terminate the threads.
-
After the sockets are closed, connect more clients without re-starting the proxy.
-
Complete each of these tasks for multiple concurrent users.
Each of these tasks is implemented entirely in Java, and can be run on any machine which supports Java.
Start by defining our Proxy class with the required imports,
indicating to other classes that it may throw an IOException:
import java.net.* ;
import java.io.* ;
import java.lang.* ;
import java.util.* ;
class Proxy {
public static void main(String args[]) throws IOException{
}
}
Read the local port, remote machine name and remote port as command line arguments (in that order):
This is done by by parsing the commands entered by the user when the Proxy Server is initailly started, picking off each peice.
The local port number is parsed first, followed by the remote host name, and finaly the remote port address.
Each of these were checked for legality, and passed to the rest of the program if acceptable.
If any were invalid, the program would exit and report the appropriate error.
// Variable to track if an error occurred
boolean errorOccurred = false;
//Variables for the host and port parameters
int localPort = -1;
int remotePort = -1;
String remoteHost = null;
try
{
Integer parseLocalPort = new Integer(args[0]);
Integer parseRemotePort = new Integer(args[2]);
localPort = parseLocalPort.parseInt(args[0], 10);
remoteHost = args[1];
remotePort = parseRemotePort.parseInt(args[2], 10);
}
catch(Exception e)
{
System.err.println("Error: " + e.getMessage());
errorOccurred = true;
}
Check the parameters for validity:
Each of these were checked for legality, and passed to the rest
of the program if acceptable.
If any were invalid, the program would exit and report the
appropriate error.
// Check for valid local and remote port, hostname not null
System.out.println("Checking: Port" + localPort +
" to " + remoteHost +
" Port " + remotePort);
if(localPort <= 0)
{
System.err.println("Error: Invalid Local Port");
error = true;
}
if(remotePort <=0)
{
System.err.println("Error: Invalid Remote Port");
error = true;
}
if(remoteHost == null)
{
System.err.println("Error: Invalid Remote Host");
error = true;
}
//If any errors so far, exit program
if(error)
System.exit(-1);
Create a socket and accept TCP requests on the local port:
This is accomplished by creating a new Server Socket Server, from the ServerSocket Class.
The port which it listens on is the one passed by the user on the command line.
An Accepting socket is created by using the Accept() method of the Server Socket Class.
This port is now ready to begin accepting TCP requests.
//Create a listening socket at proxy
ServerSocket server = null;
try
{
server = new ServerSocket(localPort);
}
catch(IOException e)
{
System.err.println("Error: " + e.getMessage());
System.exit(-1);
}
//Loop to listen for incoming connection,
//and accept if there is one
Socket incoming = null;
Socket outgoing = null;
while(true)
{
try
{
// Create the 2 sockets to transmit incoming
// and outgoing traffic of proxy server
incoming = Server.accept();
outgoing = new Socket(remoteHost, remotePort);
// Create the 2 threads for the incoming
// and outgoing traffic of proxy server
ProxyThread thread1 = new ProxyThread(incoming, outgoing);
thread1.start();
ProxyThread thread2 = new ProxyThread(outgoing, incoming);
thread2.start();
}
catch (UnknownHostException e)
{
System.err.println("Error: Unknown Host " + remotehost);
System.exit(-1);
}
catch(IOException e)
{
//continue
System.exit(-2);;
}
}
Create two threads:
This is done by implementing a new Class, ProxyThread which extends the Thread Class, and creating 2 new proxyThreads, which accept Sockets as their parameters.
The Sockets that are passed are the Incoming and Outgoing Sockets, in that order.
The Run() method of the Thread Class is overwritten, and does the actual data stream transferring, by the use of the OutputStream object, and the IpnutStream object of the Socket Class.
The Proxy accepts data on the InputStream with the use of the Read() method, and stores it in a buffer.
It then sends this buffer on the OutputStream with the use of the Write() methods of that sream.
One to handle each of the following:
-
Read an unknown amount data from the local port and forward it to the remote port:
This is done by passing the incoming and outgoing port (in that order) to the proxyThread constructor.
This creates a Listening Socket on the Proxy Server, listening on the local port, and a Sending Socket, directed to the Server who is serving the requests.
The amount of data read at each instant is printed to the screen where the proxy is running.
-
Read an unknown amount of data from the remote port and forward it to the local port:
In this case, the proxyThread class is sent the Socket parameters in the reverse order.
The Listening Socket (first parameter) is the socket connected to the Server, and the Sending Socket (second parameter) is connected to the local port of the Client.
The amount of data read at each instant is printed to the screen where the proxy is running.
Our ProxyThread Class definition:
import java.net.* ;
import java.io.* ;
import java.lang.* ;
import java.util.* ;
class ProxyThread extends Thread
{
Socket incoming, outgoing;
ProxyThread(Socket in, Socket out)
{
incoming = in;
outgoing = out;
}
// Overwritten run() method of thread,
// does the data transfers
public void run()
{
byte[] buffer = new byte[60];
int numberRead = 0;
OutputStream toClient;
InputStream fromClient;
try{
toClient = outgoing.getOutputStream();
fromClient = incoming.getInputStream();
while(true)
{
numberRead = fromClient.read(buffer, 0, 50);
if(numberRead == -1)
{
incoming.close();
outgoing.close();
}
toClient.write(buffer, 0, numberRead);
}
}
catch(IOException e)
{
}
catch(ArrayIndexOutOfBoundsException e)
{
}
}
}
Close the sockets if either closes or an exception occurs:
If any type of error is detected while the server is either started or running, it will immediately close all sockets, and terminate itself. If either the Client or the Server close their connections to the other, the Proxy Server will also close all sockets and terminate itseslf. It knows if either the Client or Server has disconnected because a -1 is sent. The Proxy Server listens, waiting for this to be sent, and when it is encountered, all sockets are closed.
Correctly terminate the threads:
Again, if any type of error is detected while the server is either started or running, it will immediately end all threads, and terminate itself. If either the Client or the Server close their connections to the other, the Proxy Server will also end all threads and terminate itseslf. It knows if either the Client or Server has disconnected because a -1 is sent. The Proxy Server listens, waiting for this to be sent, and when it is encountered, all threads are closed.
After the sockets are closed, connect more clients without re-starting the proxy:
Once the server is started it will continualy listen for new connections on the port it is listening to. Therefore, if one connection is ended, thereby closing the sockets and terminating the threads, the Proxy Server will still be active, listening for more users to connect. It does not need to be restarted in order to be connected to by more clients, because it runs independently, with or without users currently using it.
Complete each of these tasks for multiple concurrent users:
Because the server creates a new pair of threads for each client which attempts to connect to it, it can support multiple concurrent users simultaneously without any problem.
-
The Proxy Class
The proxy Class implements the Main () method of the program. It accepts and parses the users input, while doing some basic error checking. It then holds the basic information which the Proxy Server needs to run in variables, i.e the local port, remote machine, and remote port. It then creates the incoming Server Socket, outgoing Socket to the remote host, and sets the Proxy listening on the specified port for a client. When it detects a client, it creates 2 threads for that particular client, one incoming, and one outgoing.
-
The ProxyThread Class
The proxyThread Class implements the actual threads which the Proxy Server uses to send and recieve data both the Client and Server. It does the writing to and reading from of data streams, and controls the data flow from Client to Server, and vice versa. It starts the actual movement of data on the threads with the use the Run() method of the Thread Class, which contains the aforementioned functionalities.
-
The whole thing zipped