使用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加密后提交给代理服务器,代理服务器解密处理转发:)
下载完整代码:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架