之前介绍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加密后提交给代理服务器,代理服务器解密处理转发:)
下载完整代码: