A simple proxy server in C#
-
I have written a simple and minimalist HTTP proxy server that runs on command line. In the Start() method, a TcpListener blocks until it gets a client request and creates a new thread (ThreadHandleClient method) that processes this client, fetches its url and relays data. The trouble is in the relay logic that I want to refine. In the first inner loop, I receive all webserver data and send it to client until zero bytes are left. In second inner loop, I do vice-versa: receive all client data and send to web-server until zero bytes left. However, the code gets blocked in the beginning of second inner-loop at client.receive(). This is what I'm basically doing: Any help will be truly appreciated. Thanks in advance.
public void Start(IPAddress ip, int port)
{
try
{
TcpListener listener = new TcpListener(ip, port);
listener.Start(100);
while (!stopFlag)
{
Socket client = listener.AcceptSocket();
IPEndPoint rep = (IPEndPoint)client.RemoteEndPoint;
Thread th = new Thread(ThreadHandleClient);
th.Start(client);
}
listener.Stop();
}
catch (Exception ex)
{
Debug.Print("START: " + ex.Message);
}
}
public void ThreadHandleClient(object o)
{
try
{
Socket client = (Socket)o;
NetworkStream ns = new NetworkStream(client);
//RECEIVE CLIENT DATA
byte[] buffer = new byte[2048];
int rec = 0, sent = 0, transferred = 0, rport = 0;
string data = "";
do
{
rec = ns.Read(buffer, 0, buffer.Length);
data += Encoding.ASCII.GetString(buffer, 0, rec);
} while (rec == buffer.Length);
//PARSE DESTINATION AND SEND REQUEST
string line = data.Replace("\r\n", "\n").Split(new string[] { "\n" }, StringSplitOptions.None)[0];
Uri uri = new Uri(line.Split(new string[] { " " }, StringSplitOptions.None)[1]);
Debug.Print("CLIENT REQUEST RECEIVED: " + uri.OriginalString);
if (uri.Scheme == "https")
{
rport = 443;
Debug.Print("HTTPS - 443");
}
else
{
rport = 80;
Debug.Print("HTTP - 443");
}
IPHostEntry rh = Dns.GetHostEntry(uri.Host);
Socket webserver = new Socket(rh.AddressList[0].AddressFamily, SocketType.Stream, ProtocolType.IP);
webserver.Connect(new IPEndPoint(rh.AddressList[0], rport));
byte[] databytes = Encoding.ASCII.GetBytes(data);
webserver.Send(databytes, databytes.Length, SocketFlags.None);
Debug.Print("SENT TO SERVER. WILL NOW RELAY: " + data);
//STA -
I have written a simple and minimalist HTTP proxy server that runs on command line. In the Start() method, a TcpListener blocks until it gets a client request and creates a new thread (ThreadHandleClient method) that processes this client, fetches its url and relays data. The trouble is in the relay logic that I want to refine. In the first inner loop, I receive all webserver data and send it to client until zero bytes are left. In second inner loop, I do vice-versa: receive all client data and send to web-server until zero bytes left. However, the code gets blocked in the beginning of second inner-loop at client.receive(). This is what I'm basically doing: Any help will be truly appreciated. Thanks in advance.
public void Start(IPAddress ip, int port)
{
try
{
TcpListener listener = new TcpListener(ip, port);
listener.Start(100);
while (!stopFlag)
{
Socket client = listener.AcceptSocket();
IPEndPoint rep = (IPEndPoint)client.RemoteEndPoint;
Thread th = new Thread(ThreadHandleClient);
th.Start(client);
}
listener.Stop();
}
catch (Exception ex)
{
Debug.Print("START: " + ex.Message);
}
}
public void ThreadHandleClient(object o)
{
try
{
Socket client = (Socket)o;
NetworkStream ns = new NetworkStream(client);
//RECEIVE CLIENT DATA
byte[] buffer = new byte[2048];
int rec = 0, sent = 0, transferred = 0, rport = 0;
string data = "";
do
{
rec = ns.Read(buffer, 0, buffer.Length);
data += Encoding.ASCII.GetString(buffer, 0, rec);
} while (rec == buffer.Length);
//PARSE DESTINATION AND SEND REQUEST
string line = data.Replace("\r\n", "\n").Split(new string[] { "\n" }, StringSplitOptions.None)[0];
Uri uri = new Uri(line.Split(new string[] { " " }, StringSplitOptions.None)[1]);
Debug.Print("CLIENT REQUEST RECEIVED: " + uri.OriginalString);
if (uri.Scheme == "https")
{
rport = 443;
Debug.Print("HTTPS - 443");
}
else
{
rport = 80;
Debug.Print("HTTP - 443");
}
IPHostEntry rh = Dns.GetHostEntry(uri.Host);
Socket webserver = new Socket(rh.AddressList[0].AddressFamily, SocketType.Stream, ProtocolType.IP);
webserver.Connect(new IPEndPoint(rh.AddressList[0], rport));
byte[] databytes = Encoding.ASCII.GetBytes(data);
webserver.Send(databytes, databytes.Length, SocketFlags.None);
Debug.Print("SENT TO SERVER. WILL NOW RELAY: " + data);
//STAIf no data has arrived, then
Receive
is going to block. If this is not what you want, then useBeginReceive
.The difficult we do right away... ...the impossible takes slightly longer.
-
If no data has arrived, then
Receive
is going to block. If this is not what you want, then useBeginReceive
.The difficult we do right away... ...the impossible takes slightly longer.
Thanks Richard. For reasons of simplicity, I don't want to create anync calls and handle everything in thant one thread only. Is there no way to do this using the sync Receive() method ?
-
Thanks Richard. For reasons of simplicity, I don't want to create anync calls and handle everything in thant one thread only. Is there no way to do this using the sync Receive() method ?
If you want, you could call Receive on a background worker thread, but that would entail multiple threads. The bottom line is that Receive will behave synchronously, meaning it will block when there is no data.
The difficult we do right away... ...the impossible takes slightly longer.
-
If you want, you could call Receive on a background worker thread, but that would entail multiple threads. The bottom line is that Receive will behave synchronously, meaning it will block when there is no data.
The difficult we do right away... ...the impossible takes slightly longer.
Is there a way to check the data in the socket and call Receive() only when data is available for receiving. This way it should not block, right?
-
Is there a way to check the data in the socket and call Receive() only when data is available for receiving. This way it should not block, right?
I see that
Socket
has a method calledPoll
. Take a look: Socket.Poll[^]The difficult we do right away... ...the impossible takes slightly longer.
-
I see that
Socket
has a method calledPoll
. Take a look: Socket.Poll[^]The difficult we do right away... ...the impossible takes slightly longer.
Thanks. The Poll() method really helped. I'm now polling for three seconds before receiving for both client and the server. However, there is still some logical issue with my code:
if (client.Poll(3000 \* 1000, SelectMode.SelectRead)) { rec = client.Receive(buffer, buffer.Length, SocketFlags.None); Debug.Print("RECEIVED FROM CLIENT: " + Encoding.ASCII.GetString(buffer, 0, rec)); sent = webserver.Send(buffer, rec, SocketFlags.None); Debug.Print("SENT TO WEBSERVER\[" + sent.ToString() + "\]: " + Encoding.ASCII.GetString(buffer, 0, rec)); transferred += rec; } else { Debug.Print("No data polled from client"); }
As I said, one logical fault still remains. In the proxy client machine, I'm able to open google.com. Then performed a search that also went fine. However, when I click on a search result, the proxy again gives me google.com!! What am I missing?