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();
}
}
}