using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.IO; using System.Threading; using System.Net; namespace TradeIdeas.ServerConnection { /// ///Similar to MIME base 64 or uuencode, but my own choice of characters and ///padding rules. /// ///This is the used with form encoded data. We have a binary string that we ///want to send as part of a Post request. We have specifically chosen ///characters that do not need to be encoded and decoded by RawUrlEncode. ///This makes the result shorter. Potentially the code is faster because ///we can skip the encode/decode step, but the standard tools will do that ///step anyway. /// ///Strictly speaking we don't need to use the form encoding for the POST data, ///but that's supported by the tools, like PHP. And presumably that's the ///safest thing to do for any HTTP proxy servers that we might not even know ///about. /// ///Another option would be to naively use the RawUrlEncode on the binary ///string. It should work, but the average size would be bigger. And the ///size would vary more, so if we are limited to a maximum size, we often ///will not get anywhere near that maximum size. That is to say that our ///worst case will be worse than our average case. /// ///"P" of course stands for "Phil". /// /// This should be functionally equivalent to the Delphi version. (There /// is only one version of the server, and it doesn't know which client /// it is talking with.) However, the structure of this unit is different. /// The delphi version was a little more generic, taking a complete string /// as an input and returning a complete string as an output. This version /// attempts to match the data structures of the calling routine. This /// might make things more effecient by avoiding some unnecessary copies. /// But it might actually hurt performance. This version includes more /// function calls, and it replaces some local variables with member /// variables. /// class P64Encoder { private byte GetEncoding(UInt32 asInt) { // asInt should be a number between 0 and 63, inclusive. We allow it to be // 32 bits because of the way we expect it to be generated. We make it unsigned // so we can easily shift the bits without worrying about something being a // negative number. The result is a byte which could easily be interpreted as // an ASCII character. In particular, it's always a "safe" character. if (asInt < 26) // 0 --> "a", 25 --> "z", "a" == 97 in ASCII return (byte)(asInt + 97); if (asInt < 52) // 26 -> "A", 51 --> "Z", "A" == 65 in ASCII return (byte)(asInt - 26 + 65); if (asInt < 62) // 52 -> "0", 61 -> "9", "0" == 48 in ASCII return (byte)(asInt - 52 + 48); if (asInt == 62) // 62 -> "_", "_" == 95 in ASCII return 95; // 63 -> "-", "-" == 45 return 45; } private byte[] CreateEncoding() { // Is this really the fastest way to encode a byte? In the Delphi code we // built this array once, and then we could do an array lookup to encode // each byte, and that was fast. The C# code will presumably add range // checking to each array lookup. (Maybe that gets optimized away, but I // doubt it.) That could actually make things slower. It might be better // to compute this value each time it is needed. byte[] buffer = new byte[64]; int outIndex = 0; for (byte i = 97; i < 97 + 26; i++) // a - z buffer[outIndex++] = i; for (byte i = 65; i < 65 + 26; i++) // A - Z buffer[outIndex++] = i; for (byte i = 48; i < 48 + 10; i++) // 0 - 9 buffer[outIndex++] = i; buffer[outIndex++] = 95; // _ buffer[outIndex++] = 45; // - Debug.Assert(outIndex == buffer.Length); return buffer; } private int _position = 0; /* The encoding process is cyclic. The process for * the fourth byte is the same as for the first. */ private UInt32 _inProgress = 0; public byte[] OutputBuffer { get; set; } public int OutputIndex { get; set; } private void AddByte(byte toAdd) { switch (_position) { case 0: _inProgress = toAdd; // There should be 8 valid bits in there. Grab the 6 most significant // bits and leave the 2 least significant bits for next time. OutputBuffer[OutputIndex++] = GetEncoding((_inProgress >> 2) & 63); _position = 1; break; case 1: _inProgress <<= 8; _inProgress |= toAdd; // There should be 10 valid bits in there. Grab the 6 most significant // bits and leave the 4 least significant bits for next time. OutputBuffer[OutputIndex++] = GetEncoding((_inProgress >> 4) & 63); _position = 2; break; case 2: _inProgress <<= 8; _inProgress |= toAdd; // There should be 12 valid bits in there. Grab the 6 most significant // bits first, then the 6 least significant bits next. Leave nothing // of value in the buffer. OutputBuffer[OutputIndex++] = GetEncoding((_inProgress >> 6) & 63); OutputBuffer[OutputIndex++] = GetEncoding(_inProgress & 63); _position = 0; break; } } public void Flush() { switch (_position) { case 0: // No valid bits in _inProgress. Nothing to do; break; case 1: // 2 valid bits in _inProgress. Treat this just like a call to add // one byte, a 0. OutputBuffer[OutputIndex++] = GetEncoding((_inProgress << 4) & 63); break; case 2: // 4 valid bits in _inProgress. This is similar to a call to add // the byte 0, but that would produce two bytes on the output. // The second one would be all padding, so we don't send it. // This is more than an optimization. We don't explicitly send the // length of the original list of bytes. If we sent an extra // byte of encoded data here, the decoder would assume we had one // more byte in the original list. OutputBuffer[OutputIndex++] = GetEncoding((_inProgress << 2) & 63); break; } // No more data. _position = -1; } public void AddBytes(byte[] buffer, int first, int count) { while (count > 0) { AddByte(buffer[first]); first++; count--; } } public P64Encoder(byte[] outputBuffer = null, int outputIndex = 0) { OutputBuffer = outputBuffer; OutputIndex = outputIndex; } } public struct HttpOptions { public string InitialUrl; public ConnectionCallbacks ConnectionCallbacks; // We do NOT include proxy options here. By default the C# code will use the system // settings for the proxy. That is sufficient. By default the Delphi code would use // nothing for these settings. // Note: This can use an HTTP proxy. This will ignore any SOCKS proxy settings. } public class HttpConnection : IConnection { public const int DEFAULT_TIMEOUT = 6000; // 6 seconds. private static int _timeout = DEFAULT_TIMEOUT; public static int Timeout // Milliseconds. { get { return _timeout; } set { if (value <= 0) _timeout = DEFAULT_TIMEOUT; else if (value < 500) // a minimum of 500 millisecond. _timeout = 500; _timeout = value; } } private HttpOptions _options; // For simplicity I create my own threads and then use blocking calls to the network. // The non blocking calls do the same thing under the hood; they create a seperate // thread. This just seems like it will make the code simpler. private Thread _readThread; private Thread _writeThread; private AutoResetEvent _writeQueueReady = new AutoResetEvent(false); // _sendQueue doubles as a mutex. private List _sendQueue = new List(); // This is a request for the threads to shut down. This is polled, so it is not instant. private bool _pleaseStopNow; private bool FindBreaks(byte[] data, int length, out byte[] writeUrlPart, out byte[] passThroughData) { // We are looking for a pattern of ^.*\r\n(.*)\r\n(.*)$ // Our tools are limited because we are working with an array of bytes, not a string. writeUrlPart = null; passThroughData = null; int firstCR = -1; int secondCR = -1; int i = 0; foreach (byte b in data) { if (i >= length) break; if (b == '\r') if (firstCR == -1) firstCR = i; else { secondCR = i; break; } i++; } if (secondCR == -1) // We did not find two possible breaks. return false; if (secondCR == length - 1) // The data ends at the second CR. So there is no room for a // second LF. return false; if ((data[firstCR + 1] != '\n') || (data[secondCR + 1] != '\n')) // The data does not match the expected format. We expect each CR to be followed // by an LF. It's not 100% clear what to do here. It's tempting to throw an exception // because we know it will never get better. For simplicity I just return false. return false; // The first part, before the first CRLF, is padding. Throw it away. // The second part, between the first and second CRLF, is the URL used for writing. // That is what we are trying to extract. writeUrlPart = new byte[secondCR - firstCR - 2]; Array.Copy(data, firstCR + 2, writeUrlPart, 0, writeUrlPart.Length); // The third part, after the second CRLF, is the first of the passthrough data. // That, and everything after it, is copied as the the listener. Typically // we'd expect this to be empty. Our normal alert server does not send any data // out until it receives the first data from the client. And the HTTP tunnel // only attaches to a small number of servers. But this should still work even // if we do get data right away. if (secondCR + 2 < length) { passThroughData = new byte[data.Length - secondCR - 2]; Array.Copy(data, secondCR + 2, passThroughData, 0, passThroughData.Length); } return true; } private string _writeUrl; private void SendData(byte[] data) { SendData(data, data.Length); } private void SendData(byte[] data, int length) { try { _options.ConnectionCallbacks.OnRead(data, length, this); } catch { } } private void DoReadThread() { try { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_options.InitialUrl); request.Timeout = Timeout; // This is very strange. I have no problems creating a lot of requests one after // another in the write thread. But in the read thead, by default, my first connection // works and my second one will time out. If I raise the number of connections allowed // for the service point, that will help a little. But after restarting enough times // in a row, I will run out of connections, and new connections will time out again. // Adding this line prevents the problem. Adding this line means that we do not // save an old connection. This works, but it doesn't make a lot of sense. I tried // several things to do a better job cleaning up, like aborting the reqest at the // end of this routine, but nothing helped. If you waited long enough (I'm not // exactly sure, but around 10 minutes) it would eventually reconnect on it's own. request.ServicePoint.MaxIdleTime = 0; // This next line is tricky. It gave us some releif when we were trying to reset // the connection from within the client. But it was limited. The line above did a // better job. In fact, the line above seemed to completely solved the problem when // the user chooses to reconnect. However, we would sometimes lock up in the same // way when the server kicked us off. This line seems to help in the case when we // get cut off. request.ServicePoint.ConnectionLimit = 20; WebResponse webResponse = request.GetResponse(); Stream fromServer = webResponse.GetResponseStream(); // Look for the header. byte[] buffer = new byte[2048]; int bufferOffset = 0; while (!_pleaseStopNow) { if (bufferOffset == buffer.Length) { byte[] newBuffer = new byte[buffer.Length * 2]; Array.Copy(buffer, newBuffer, buffer.Length); buffer = newBuffer; } int readCount = fromServer.Read(buffer, bufferOffset, buffer.Length - bufferOffset); if (readCount <= 0) { Disconnect(); return; } bufferOffset += readCount; byte[] writeUrlPart; byte[] passThroughData; if (FindBreaks(buffer, bufferOffset, out writeUrlPart, out passThroughData)) { _writeUrl = Encoding.UTF8.GetString(writeUrlPart); _writeThread = new Thread(DoWriteThread); _writeThread.IsBackground = true; _writeThread.Start(); if (null != passThroughData) SendData(passThroughData); break; } } // Look for the passthrough data. while (!_pleaseStopNow) { int readCount = fromServer.Read(buffer, 0, buffer.Length); if (readCount <= 0) { Disconnect(); return; } SendData(buffer, readCount); } } catch (Exception ex) { _debugSaveException = ex; Disconnect(); } } private static Exception _debugSaveException; private bool SomethingInTheQueue() { if (null != _partiallySent) return true; lock (_sendQueue) { return _sendQueue.Count > 0; } } private int _nextSequenceNumber; private byte[] _partiallySent; private int _partiallySentCount; private const int MAX_SEND_BYTES = 6000; private byte[] CreateWriteMessage(out int messageSize) { byte[] prefix = Encoding.ASCII.GetBytes("sequence=" + _nextSequenceNumber + "&body="); _nextSequenceNumber++; byte[] message = new byte[10000]; Array.Copy(prefix, message, prefix.Length); P64Encoder encoder = new P64Encoder(message, prefix.Length); int bodySize = 0; int readFromQueue = 0; lock (_sendQueue) { while (true) { // Iterate over the items that we might want to send. Start with the value that might be in // _partiallySent. Then continue with the items in _sendQueue. Stop when we run out of space // in the outgoing message or when we run out of data. If we partially send an array, leave // that in _partiallySent so we can get it next time. if (null != _partiallySent) { int bytesToSend = Math.Min(MAX_SEND_BYTES - bodySize, _partiallySent.Length - _partiallySentCount); encoder.AddBytes(_partiallySent, _partiallySentCount, bytesToSend); _partiallySentCount += bytesToSend; if (_partiallySentCount == _partiallySent.Length) // This array was completely sent. We are done with it. _partiallySent = null; bodySize += bytesToSend; } if (bodySize >= MAX_SEND_BYTES) break; if (readFromQueue >= _sendQueue.Count) break; _partiallySent = _sendQueue[readFromQueue]; _partiallySentCount = 0; readFromQueue++; } if (readFromQueue > 0) _sendQueue.RemoveRange(0, readFromQueue); } encoder.Flush(); messageSize = encoder.OutputIndex; //string debug = Encoding.ASCII.GetString(message, 0, messageSize); return message; } private void DoWriteThread() { while (!_pleaseStopNow) { while (!SomethingInTheQueue()) _writeQueueReady.WaitOne(); int messageSize; byte[] message = CreateWriteMessage(out messageSize); int retryCount = 0; while (!_pleaseStopNow) { if (retryCount > 0) try { // The Delphi code said that any attempt at a meaningful message // only added to the confusion. _options.ConnectionCallbacks.OnAutoRetry(this, "HTTP auto retry"); } catch { } retryCount++; if (retryCount > 3) { Disconnect(); return; } try { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_writeUrl); request.Timeout = Timeout; request.ReadWriteTimeout = Timeout; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; using (Stream requestStream = request.GetRequestStream()) { using (BinaryWriter requestWriter = new BinaryWriter(requestStream)) { requestWriter.Write(message, 0, messageSize); } } using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode == HttpStatusCode.OK) // Move on. break; } } catch (Exception ex) { // try again _debugSaveException = ex; continue; } } } } public HttpConnection(HttpOptions options) { _options = options; if ((null == _options.InitialUrl) || ("" == _options.InitialUrl)) _options.InitialUrl = "http://proxy.trade-ideas.com/Read.php"; Debug.Assert(null != _options.ConnectionCallbacks); } static HttpConnection() { // This was not an issue when talking directly with the old web server on karen. // When I tried to talk with the newer web server on bob-saget, which was behind // a lighttpd proxy, I was getting 417 “Expectation Failed.” on every write. // According to the internet, this is a common problem with C# and web proxy // servers. // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c System.Net.ServicePointManager.Expect100Continue = false; } /// /// Disconnect /// public void Disconnect() { lock (_sendQueue) { if (_pleaseStopNow) // Already disconnected. This is allowed, but it doesn't do anything. return; _pleaseStopNow = true; } _writeQueueReady.Set(); // Interesting. The reader might send messages after the close message. Is that a problem? try { _options.ConnectionCallbacks.OnClosed(this); } catch { } } /// /// Connect /// public void Connect() { lock (_sendQueue) { Debug.Assert(null == _readThread, "Duplicate call to Connect()!"); _readThread = new Thread(DoReadThread); } _readThread.IsBackground = true; _readThread.Start(); } /// /// Send Message /// /// public void Send(List messages) { lock (_sendQueue) { _sendQueue.AddRange(messages); } StartSend(); } /// /// Send Message /// /// public void Send(byte[] message) { lock (_sendQueue) { _sendQueue.Add(message); } StartSend(); } private void StartSend() { _writeQueueReady.Set(); } } }