using System; using System.Net; using System.Net.Sockets; using System.Text; using System.IO; using System.Text.RegularExpressions; using System.Collections; using System.Threading; namespace Danga { // get a file name from the client // open the file and send the // contents from the server to the client public class MonoBal { int inChunks = 0; // chunks in queues from clients to proxies (POSTs) int outChunks = 0; // chunks in queues from proxies to clients (responses) struct Chunk { public int Size; public byte[] Buf; public Chunk (int size, byte[] buf) { Size = size; Buf = buf; } } class ClientHandler { const int BufferSize = 4096; byte[] buffer; Socket cSock; UsefulStream cUS; // for reading headers/POST data NetworkStream cNS; // stream for writing back responses Encoding encISO; // 8bit preserving, HTTP standard Queue inQ; Queue outQ; // Client State private enum ClientState { ReadingHeaders, ReadingRest, Writing, }; private const int MaxHeaders = 4096; private bool headersRead = false; string clientMethod, clientURI, clientHTTPVer; string clientRequest = ""; private int clientRead = 0; // bytes read from client, zeroed at each reading stage private AsyncCallback clientReadCallback; private byte[] requestBytes; // Proxy State NetworkStream pNS; Socket pSock; // constructor public ClientHandler(Socket socketForClient) { cSock = socketForClient; cUS = new UsefulStream(socketForClient); // for reading cNS = new NetworkStream(cSock); encISO = Encoding.GetEncoding("ISO-8859-1"); inQ = Queue.Synchronized(new Queue()); outQ = Queue.Synchronized(new Queue()); } public void StartRead () { cUS.BeginGetLine(new AsyncCallback(this.OnGetRequestLine), 0, null); } // gets the first line of an HTTP request. // doesn't allow proxy requests. just /abs_path or *. HTTP version is optional and // otherwise assumes old 0.9-style private void OnGetRequestLine (IAsyncResult ar) { string s = cUS.EndGetLine(); Regex reVerb = new Regex(@"^(?\w+) (?(?:\*|(?:/\S*?)))(?: HTTP/(?\d+\.\d+))?\r?\n?$"); Match m = reVerb.Match(s); if (! m.Success) { this.Close(); return; } clientRequest = s; clientMethod = m.Groups["method"].ToString(); clientURI = m.Groups["uri"].ToString(); clientHTTPVer = m.Groups["ver"].ToString(); cUS.BeginGetLine(new AsyncCallback(this.OnGetHeader), 0, null); } private void OnGetHeader (IAsyncResult ar) { String s = cUS.EndGetLine(); if (s == null) { Close(); return; } // temporary clientRequest = clientRequest + s; if (clientRequest.Length > 4096) { Close(); return; } String ds = s.Replace("\r\n", "").Replace("\n", ""); //Console.WriteLine("Header({0} bytes): [{1}]", s.Length, ds); if (s == "\n" || s == "\r\n") { headersRead = true; ProxyConnect(); return; } cUS.BeginGetLine(new AsyncCallback(this.OnGetHeader), 0, null); } private void ProxyConnect () { IPAddress kenny = IPAddress.Parse("10.1.0.2"); IPEndPoint pxEP = new IPEndPoint(kenny, 80); pSock = new Socket(pxEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); pSock.BeginConnect(pxEP, new AsyncCallback(ProxyConnected), pSock); } private void ProxyConnected ( IAsyncResult ar ) { if (! pSock.Connected) { Console.WriteLine("Proxy connect errro!"); Close(); return; } pNS = new NetworkStream(pSock); byte[] req = encISO.GetBytes(clientRequest); ProxyWrite(req); } byte[] proxyWriteBuf; byte[] proxyReadBuf; private void onProxyWrite (IAsyncResult ar) { pNS.EndWrite(ar); proxyWriteBuf = null; //Console.WriteLine("Done proxy writing!"); // FIXME: haven't pushed POST/PUT response yet (just headers) // but we're just testing GET now, so time to read from proxy... ProxyReadMore(); } private void ProxyReadMore () { proxyReadBuf = new byte[4096]; pNS.BeginRead(proxyReadBuf, 0, 4096, new AsyncCallback(this.onProxyRead), null); } private void onProxyRead (IAsyncResult ar) { int bytesRead = pNS.EndRead(ar); //Console.WriteLine("proxy read = {0}", bytesRead); if (bytesRead == 0) { pNS.Close(); if (pSock.Connected) pSock.Close(); pSock = null; pNS = null; // signal the end of the stream with a 0/null chunk outQ.Enqueue(new Chunk(0, null)); } else { outQ.Enqueue(new Chunk(bytesRead, proxyReadBuf)); } ClientPush(); if (bytesRead > 0) { // TODO: and not too much in the queue ProxyReadMore(); } } private void ProxyWrite (byte[] buf) { if (buf != null) { if (buf != proxyWriteBuf) { proxyWriteBuf = buf; } pNS.BeginWrite(proxyWriteBuf, 0, proxyWriteBuf.Length, new AsyncCallback(this.onProxyWrite), null); } } int clientWriting = 0; private void ClientPush () { if (Interlocked.CompareExchange(ref clientWriting, 1, 0) > 0) return; //Console.WriteLine("clientWriting!"); if (outQ.Count == 0) { clientWriting = 0; //Console.WriteLine("outQ empty"); return; } Chunk c = (Chunk) outQ.Dequeue(); //Console.WriteLine("client push = {0}", c.Size); if (c.Size != 0) { try { cNS.BeginWrite(c.Buf, 0, c.Size, new AsyncCallback(this.onClientWrite), null); } catch { Console.WriteLine("exception after cNS.BeginWrite"); Close(); return; } } else { // done! //Console.WriteLine("Done!"); Close(); return; } } private void onClientWrite (IAsyncResult ar) { try { cNS.EndWrite(ar); } catch { Console.WriteLine("Client died or something"); Close(); return; } clientWriting = 0; //Console.WriteLine("did client write"); ClientPush(); } private void Close () { try { cNS.Close(); cNS = null; cUS.Close( ); cUS = null; } catch { Console.Write("exception closing socket?"); } if (pSock != null && pSock.Connected) pSock.Close(); pSock = null; } } private void Run( ) { IPAddress localAddr = IPAddress.Parse("127.0.0.1"); TcpListener tcpListener = new TcpListener(localAddr, 65000); tcpListener.Start( ); for (;;) { Socket socketForClient = tcpListener.AcceptSocket(); if (socketForClient.Connected) { //Console.WriteLine("Client connected"); ClientHandler handler = new ClientHandler(socketForClient); handler.StartRead( ); } } } public static void Main () { MonoBal app = new MonoBal(); app.Run(); } } // public class MonoBal } // namespace Danga