之前介绍Beetle的应用都是基于自定义消息,那给人的感就是这个组件只能做这方面的用途.其实Beetle只是一个基础的Tcp组件,在它的基础上可以完成其他协议通讯上的工作.以下是通过Beetle简单地制作一个http代理服务器,为了验证这个代理服务器的功能编写这文章也是通过http代理服务器来提交.以下描述Beetle实现这个功能.
首先Beetle并没有提供Http协议的封装,为了对http协议进行分析必须实现一个简单的http协议分析器.组件提供了Package基础类和其扩展的EofDataOfPackage和HeadSizeOfPackage;http是一种基于结束符的协方方式,可以直接从EofDataOfPackage派生下来.以下是一个http协议分析类的简单实现
public class HttpPackage:Beetle.EofDataOfPackage
{
public HttpPackage(Beetle.TcpChannel channel)
: base(channel)
{
}
public static Beetle.ByteArrayPool BlockByteArrayPool = new Beetle.ByteArrayPool(200, Beetle.TcpUtils.ReceiveBufferLength);
protected override Beetle.IMessage GetMessage(string name)
{
return new HttpMessage();
}
private HttpMessage httpMsg = null;
private int mContentLength = 0;
public override void Import(byte[] data, int start, int count)
{
int rcount = 0;
if (httpMsg == null)
{
httpMsg = new HttpMessage();
int httpendindex = ByteIndexOf(data, EofData);
if (httpendindex < 0)
throw Beetle.NetTcpException.ReadDataError(null);
rcount = httpendindex + 1;
loadHttpInfo(Encoding.ASCII.GetString(data, 0, rcount), httpMsg);
OnReceiveMessage(httpMsg);
if (!httpMsg.HasBody && !httpMsg.IsGzip)
httpMsg = null;
}
if (rcount < count && mContentLength > 0)
{
HttpBodyBlock block = new HttpBodyBlock();
block.Data = BlockByteArrayPool.Pop();
Buffer.BlockCopy(data, start + rcount, block.Data.Array, 0, count - rcount);
mContentLength = mContentLength - (count - rcount);
block.Data.SetInfo(0, count - rcount);
OnReceiveMessage(block);
mContentLength = mContentLength - (count - rcount);
if (mContentLength == 0)
httpMsg = null;
}
else if (rcount < count)
{
HttpBodyBlock block = new HttpBodyBlock();
block.Data = BlockByteArrayPool.Pop();
Buffer.BlockCopy(data, start + rcount, block.Data.Array, 0, count - rcount);
mContentLength = mContentLength - (count - rcount);
block.Data.SetInfo(0, count - rcount);
OnReceiveMessage(block);
}
}
private void loadHttpInfo(string info, HttpMessage http)
{
string[] properties = info.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
http.HttpMethod = properties[0];
mContentLength = 0;
for (int i = 1; i < properties.Length; i++)
{
string property = properties[i];
int index = property.IndexOf(':');
HttpMessage.Header header = new HttpMessage.Header();
header.Name = property.Substring(0, index);
header.Value = property.Substring(index + 1, (property.Length - index - 1));
http.Headers.Add(header);
if (header.Name == "Content-Length")
{
mContentLength = int.Parse(header.Value.Trim());
http.HasBody = true;
}
if (header.Name == "Host")
{
string[] values = header.Value.Split(':');
http.Host = values[0].Trim();
if (values.Length > 1)
http.Port = int.Parse(values[1].Trim());
}
if (header.Name == "Proxy-Connection")
{
header.Name = "Connection";
}
if (header.Name == "Content-Encoding")
{
if (header.Value.IndexOf("gzip", StringComparison.InvariantCultureIgnoreCase) >= 0)
http.IsGzip = true;
}
}
if (!string.IsNullOrEmpty(http.Host))
http.HttpMethod = http.HttpMethod.Replace("http://" + http.Host.Trim(), "");
}
public override void MessageWrite(Beetle.IMessage msg, Beetle.BufferWriter writer)
{
msg.Save(writer);
if (msg is HttpBodyBlock)
{
BlockByteArrayPool.Push(((HttpBodyBlock)msg).Data);
}
}
private static byte[] mEofData = Encoding.ASCII.GetBytes("\r\n\r\n");
protected override byte[] EofData
{
get { return mEofData; }
}
}
消息分析比较简单,首先是看一下存不存在Http请求或应答信息,如果不存在则先获取http相关信息.后面就是根据ContentLength来获取内容数据,包括提交数据.不过对于应答来说有些gzip打开的情况下是不存在ContentLength的.大体上一个http协议的分析就完成了.
协议分析完成后下面就是代理交互部分实现,当一个请求接入的时候就根据当前http的请求信息产生一个新目的端的连接
private void onRequestReceive(Beetle.PacketRecieveMessagerArgs e)
{
SendToTarget(e.Message);
if (e.Message is HttpPackage.HttpMessage)
{
HttpPackage.HttpMessage http = (HttpPackage.HttpMessage)e.Message;
if (mTargetChannel == null)
{
createTarget(http);
}
}
}
private void createTarget(HttpPackage.HttpMessage http)
{
try
{
System.Net.IPAddress[] ipaddress = System.Net.Dns.GetHostAddresses(http.Host);
Beetle.TcpServer.CreateClientAsync(ipaddress[0], http.Port, OnTargetCreate);
}
catch (Exception e_)
{
Console.WriteLine(e_.Message);
Dispose();
}
}
判断当前连接对应的目的端连接是否存在,如果不存在则创建连接.创建完成后就是两个连接数据转发交互了.
private void onTargetReceive(Beetle.PacketRecieveMessagerArgs e)
{
if (e.Message is HttpPackage.HttpMessage)
{
HttpPackage.HttpMessage http = (HttpPackage.HttpMessage)e.Message;
Console.WriteLine("{0} Response to {1}", mTargetChannel.EndPoint, mRequestChannel.EndPoint);
Console.Write(http);
ResponseHttps.Add(http);
mRequestChannel.Send(http);
}
else
{
HttpPackage.HttpBodyBlock block = (HttpPackage.HttpBodyBlock)e.Message;
mRequestChannel.Send(block);
Console.WriteLine("{0} Response to {1}", mTargetChannel.EndPoint, mRequestChannel.EndPoint);
Console.WriteLine(" Response DataLength:" + block.Data.Count);
}
}
到这里一个http代理服务器就已经完成了,这只是很普通的http代理功能,对于https不起作用.如果要做一个很完善的http代理服务器那首先要对http协议有个详细的了解,对没释放连处理,内存使用回收等.这里只是作为一个sample简单的制作.以下是通过这个代理服务查看www.163.com的效果,如果你想用他来FQ那不行:)对于http这此协议墙是一定会拦到的,如果要FQ那就做个client在本地获了http加密后提交给代理服务器,代理服务器解密处理转发:)

下载完整代码: