使用Beetle实现http代理服务

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

下载完整代码:

ProxyServer.rar (210.98 kb)

posted @ 2012-04-19 14:28  beetlex  阅读(1549)  评论(3编辑  收藏  举报