Introduction
I'm a newbie in network programming. So, I tried to goal my learning into making a very simple proxy server. This one is very simple, an HTTP proxy server using lib ACE.
Background
You need to know socket and multi-thread programming, and also about lib ACE. I use lib ACE for the socket and the logger. Actually, I started with an asynchronous proxy server (single thread with non-blocking socket) but I ended up with a very slow proxy server. That's why I am using this library. So, it is better for you to download lib ACE first and build the library both for the debug and release versions. Lib ACE is, however, a very nice library for network programming. I have used lib ACE as a dynamic library here.
Basic Theory
Well, here is a little theory:
A proxy server is a server (a computer system or an application program) that services the requests of its clients by forwarding requests to other servers. A client connects to the proxy server, requesting some service, such as a file, connection, Web page, or other resource, available from a different server. The proxy server provides the resource by connecting to the specified server and requesting the service on behalf of the client (Quoted from Wikipedia.org).
So, how will we do it? Here is the plan.
Create a TCP socket to the client, your Web browser I mean. Bind on port 5060, and start listening for incoming requests from the client.
- Soon after a connection from the client is accepted, the proxy will store its socket data into a buffer variable. This happens in an infinite loop.
- A thread, called mother thread, will always run, checking the buffer. If the buffer is not empty, then it will create another thread, called daughter thread.
- A daughter thread will receive requests from the client based on its stored socket data.
- The proxy will process this request and determine the destination IP address and port number (commonly 80 for HTTP).
- Then, the request is sent to the destination Web server.
- Soon, the Web server will reply, sending back the content of the requested pages.
- The proxy will keep receiving this data and then send it back to the client until one of them (client or server) closes the connection or until all the page contents have been sent.
The Code
Everything starts here. There is an infinite loop that will accept any incoming connection and put its socket information into a buffer list. At the same time, mother thread is running, watching on that buffer list.
int CPROXY::Run(int nPort)
{
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Running!\n")));
DWORD thid;
HANDLE hMotherThread = CreateThread(NULL, 0, MotherThread, this, 0, &thid);
if (!hMotherThread)
return -1;
ACE_SOCK_Stream client_stream;
ACE_INET_Addr addr;
addr.set(nPort, addr.get_ip_address());
int e = client_acceptor.open(addr);
if (e == INVALID_SOCKET)
return -1;
while(true)
{
int e = client_acceptor.accept(client_stream);
if (e == INVALID_SOCKET)
continue;
EnterCriticalSection(&guard);
queue.push_back(client_stream);
LeaveCriticalSection(&guard);
}
return 0;
}
Besides mother thread, we also have daughter thread. The real things happen in the daughter. The mother will only do request buffer checking.
int CPROXY::MotherThreadWorker()
{
isMotherThreadRunning = true;
while (isMotherThreadRunning)
{
EnterCriticalSection(&guard);
bool isEmpty = queue.empty();
LeaveCriticalSection(&guard);
if (!isEmpty){
DWORD thid;
HANDLE hDaughterThread = CreateThread(NULL, 0,
DaughterThread, this, 0, &thid);
if (!hDaughterThread)
continue;
WaitForSingleObject(wait, INFINITE);
}
}
return 0;
}
int CPROXY::DaughterThreadWorker()
{
char buf1[BUFSIZE];
char cServerAddress[256];
int nServerPort;
EnterCriticalSection(&guard);
ACE_SOCK_Stream client_stream = queue.front();
queue.pop_front();
LeaveCriticalSection(&guard);
SetEvent(wait);
const ACE_Time_Value t(2,0);
size_t nLen = client_stream.recv(buf1, sizeof(buf1), &t);
if(sizeof(buf1) > nLen)
buf1[nLen+1] = '\0';
if (nLen <= 0){
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Connection closed by "
"browser client while receiving requests.\n")));
return -1;
}
GetAddressAndPort(buf1, cServerAddress, &nServerPort);
ACE_INET_Addr addr;
addr.set(nServerPort, cServerAddress);
ACE_SOCK_Stream server_stream;
if (server_connector.connect(server_stream, addr) == INVALID_SOCKET){
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Connection closed "
"by remote server while connecting.\n")));
return -1;
}
if (server_stream.send(buf1, nLen) == INVALID_SOCKET){
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Connection closed by remote"
" server while sending requests.\n")));
return -1;
}
char buf2[BUFSIZE];
while(1){
nLen =server_stream.recv(buf2, sizeof(buf2), &t);
if (nLen <= 0){
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Connection closed by remote "
"server while receiving responses.\n")));
break;
}
if (client_stream.send((buf2), nLen) == SOCKET_ERROR){
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Connection closed by browser "
"client while sending responses.\n")));
break;
}
}
server_stream.close();
client_stream.close();
return 0;
}
Well, from the source code, we can see that the proxy will only do bypassing requests from the client (Internet browser) to the Web server and then send back the response from the Web server to the client.
Points of Interest
Well, actually this is simple but very nice. Very good for a newbie like me to learn. Looking forward to comments...
History
- 2nd November, 2008: Initial post
- 3rd November, 2008: Put in more detailed description to make it easier to understand