使用Beetle实现http代理服务

    之前介绍Beetle的应用都是基于自定义消息,那给人的感就是这个组件只能做这方面的用途.其实Beetle只是一个基础的Tcp组件,在它的基础上可以完成其他协议通讯上的工作.以下是通过Beetle简单地制作一个http代理服务器,为了验证这个代理服务器的功能编写这文章也是通过http代理服务器来提交.以下描述Beetle实现这个功能.

    首先Beetle并没有提供Http协议的封装,为了对http协议进行分析必须实现一个简单的http协议分析器.组件提供了Package基础类和其扩展的EofDataOfPackage和HeadSizeOfPackage;http是一种基于结束符的协方方式,可以直接从EofDataOfPackage派生下来.以下是一个http协议分析类的简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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的请求信息产生一个新目的端的连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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();
 
    }
}

判断当前连接对应的目的端连接是否存在,如果不存在则创建连接.创建完成后就是两个连接数据转发交互了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 @   beetlex  阅读(1557)  评论(3编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示