加入收藏 设为首页
博客 友圈 商城
留言 搜索 投搞
首页 | 网络动态 | 技术文章 | 下载中心 | 设计 | 摄影 | 精彩Flash | 摄影作品 | 顶客排行 | 悠乐论坛
>首页 -> 技术文章 -> 网络技巧

TOP

粗糙的C#版HTTP代理
[ 录入者:riqukiqpl | 时间:2008-08-04 16:42:13 | 作者: | 来源: | 浏览:60次 ]
简介:很久以前答应给KJ写个C#版的代理类,结果一直拖着,直到从socks5代理降低要求为HTTP代理。最近稍微空一点,晚上敲了敲代码,做了个简陋的东西。不过写这个东西才发现,原来我对HTTP协议并不是那么了解,这个还是..

很久以前答应给KJ写个C#版的代理类,结果一直拖着,直到从socks5代理降低要求为HTTP代理。最近稍微空一点,晚上敲了敲代码,做了个简陋的东西。不过写这个东西才发现,原来我对HTTP协议并不是那么了解,这个还是有许多问题的,不过我会继续改进,维护这段代码——虽然以前说过类似的没做到,但是这次是认真的。

前些时候一个无锡人在我博客留言,说我读再多的书,也改变不了我是个程序员的本质。或许它觉得它是在鄙视我,不过我到觉得它是在赞扬我——我真的算不上是个程序员,写代码的能力实在是太烂了——不过我会努力的。

说说这段代码的问题吧。首先是字符串切割的问题,要将客户端提交过来的GET,POST等原始请求切割,分离出主机名,端口,URL等数据。这里用正则匹配是最好的,遗憾的是我不擅长此道,所以使用了手动切割的办法,很笨重繁琐,但是毕竟它工作得很好。第二个问题是Keep-Alive的问题,这里我没有处理好。最开始我在http proxy里面修改客户端请求,强行将keep-alive修改为close,但是发现在某些站点的时候会出错。于是使用了类似select的方法读取数据,直到超时关闭两端的连接。我猜测,这里如果解析content-length会更好,但是略微繁琐了点,还是等我仔细阅读下RFC再看怎么修改吧。第三个是CONNECT方法的问题,这个到很简单,转发数据就行了,因此是这个代码中写得最好的一部分,用来登陆QQ还是不错的。

说实话,我不喜欢HTTP这种太宽松的协议,感觉灵活得让我难以把握。直接看代码吧,我加了很多debug信息,真的要用就去掉好了。调用这个类很简单,看main函数的实现就好了。为了方便贴代码,我写的时候就把三个类写到一个文件里面去了。顺便要说的是,虽然有类,但是没有任何面向对象的东西——这也再次证明,其实我算不上一个程序员,最多是个代码爱好者。

  1. using System;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. using System.Text;
  5. using System.IO;
  6. using System.Threading;
  7. using System.Collections;
  8. namespace HttpProxy
  9. {
  10. public class HttpProxy
  11. {
  12. int ProxyPort;
  13. /// <summary>
  14. /// 代理服务器入口类构造函数
  15. /// </summary>
  16. /// <param name="Port">Http Proxy监听的端口</param>
  17. public HttpProxy( int Port)
  18. {
  19. ProxyPort = Port;
  20. }
  21. /// <summary>
  22. /// 启动Http代理服务器
  23. /// </summary>
  24. public void Start( )
  25. {
  26. TcpListener tcplistener = null;
  27. try
  28. {
  29. // 开始监听端口
  30. tcplistener = new TcpListener(Dns.GetHostAddresses(Dns.GetHostName())[0], ProxyPort);
  31. tcplistener.Start();
  32. Console.WriteLine("侦听端口号: " + ProxyPort.ToString());
  33. }
  34. catch (Exception e)
  35. {
  36. Console.WriteLine("启动代理服务器失败: " + e.Message);
  37. }
  38. while (true)
  39. {
  40. try
  41. {
  42. // 接受客户端连接
  43. Socket socket = tcplistener.AcceptSocket();
  44. HttpSession Session = new HttpSession(socket);
  45. // 启动新线程,处理连接
  46. Thread thread = new Thread(new ThreadStart(Session.Start));
  47. thread.Start();
  48. }
  49. catch( Exception e )
  50. {
  51. Console.WriteLine("接受客户端连接异常: " + e.Message );
  52. }
  53. }
  54. }
  55. }
  56. public class HttpSession
  57. {
  58. // 客户端socket
  59. Socket ClientSocket;
  60. // 设定编码
  61. Encoding ASCII = Encoding.ASCII;
  62. /// <summary>
  63. /// 构造函数
  64. /// </summary>
  65. /// <param name="socket">客户端socket</param>
  66. public HttpSession(Socket socket)
  67. {
  68. this.ClientSocket = socket;
  69. }
  70. public void Start()
  71. {
  72. // 客户端缓冲区,读取客户端命令
  73. Byte[] ReadBuff = new byte[1024 * 10];
  74. try
  75. {
  76. int Length = ClientSocket.Receive(ReadBuff);
  77. // 没有读到数据
  78. if (0 == Length)
  79. {
  80. Console.WriteLine("从客户端读取命令错误");
  81. ClientSocket.Shutdown(SocketShutdown.Both);
  82. ClientSocket.Close();
  83. return;
  84. }
  85. }
  86. // 读取出现异常
  87. catch (Exception e)
  88. {
  89. Console.WriteLine("读取客户端异常: " + e.Message);
  90. }
  91. // 来自客户端的HTTP请求字符串
  92. string ClientMsg = ASCII.GetString(ReadBuff);
  93. // 根据rnrn截取请求行
  94. string Line = ClientMsg.Substring(0, ClientMsg.IndexOf("rn"));
  95. string[] CmdArray = Line.Split(' ');
  96. // GET http://www.test.com:80/index.php HTTP/1.1
  97. // CONNECT www.test.com:443 HTTP/1.1
  98. string Cmd = CmdArray[0];
  99. string RawUrl = CmdArray[1];
  100. Console.WriteLine("原始请求: {0}", Line);
  101. // CONNECT请求
  102. if (Cmd == "CONNECT")
  103. {
  104. DoConnect(RawUrl);
  105. }
  106. // GET,POST和其他
  107. else
  108. {
  109. DoOther(RawUrl, ClientMsg);
  110. }
  111. }
  112. /// <summary>
  113. /// 处理CONNECT命令,此处作用是支持QQ,MSN,以及多级代理串联等
  114. /// </summary>
  115. /// <param name="RawUrl"></param>
  116. private void DoConnect( string RawUrl )
  117. {
  118. string[] Args = RawUrl.Split( ':' );
  119. string Host = Args[0];
  120. int Port = int.Parse(Args[1]);
  121. Socket ServerSocket = null;
  122. try
  123. {
  124. IPAddress[] IpList = Dns.GetHostEntry(Host).AddressList;
  125. Console.WriteLine("尝试连接{0}:{1}", IpList[0], Port);
  126. ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  127. ServerSocket.Connect(IpList[0], Port);
  128. }
  129. catch (Exception e)
  130. {
  131. Console.WriteLine("连接真实服务器异常: " + e.Message);
  132. }
  133. // 连接真实服务器成功
  134. if (ServerSocket.Connected)
  135. {
  136. ClientSocket.Send( ASCII.GetBytes("HTTP/1.0 200 Connection establishedrnrn") );
  137. }
  138. else
  139. {
  140. ClientSocket.Shutdown(SocketShutdown.Both);
  141. ClientSocket.Close();
  142. }
  143. // 开始转发数据
  144. ForwardTcpData(ClientSocket, ServerSocket);
  145. }
  146. /// <summary>
  147. /// 处理GET,POST等命令。使用了POLL,在代理服务器中强制去掉了Keep-Alive能力
  148. /// </summary>
  149. /// <param name="RawUrl"></param>
  150. /// <param name="ClientMsg"></param>
  151. public void DoOther(string RawUrl, string ClientMsg)
  152. {
  153. RawUrl = RawUrl.Substring(0 + "http://".Length);
  154. int Port;
  155. string Host;
  156. string Url;
  157. // 下面是分割处理请求,此处应该用正则匹配,不过我不擅长,因此手动切割,—_—!
  158. int index1 = RawUrl.IndexOf(':');
  159. // 没有端口
  160. if (index1 == -1)
  161. {
  162. Port = 80;
  163. int index2 = RawUrl.IndexOf('/');
  164. // 没有目录
  165. if (index2 == -1)
  166. {
  167. Host = RawUrl;
  168. Url = "/";
  169. }
  170. else
  171. {
  172. Host = RawUrl.Substring(0, index2);
  173. Url = RawUrl.Substring(index2);
  174. }
  175. }
  176. else
  177. {
  178. int index2 = RawUrl.IndexOf('/');
  179. // 没有目录
  180. if (index2 == -1)
  181. {
  182. Host = RawUrl.Substring(0, index1);
  183. Port = Int32.Parse(RawUrl.Substring(index1 + 1));
  184. Url = "/";
  185. }
  186. else
  187. {
  188. // /出现在:之前,则说明:后面的不是端口
  189. if (index2 < index1)
  190. {
  191. Host = RawUrl.Substring(0, index2);
  192. Port = 80;
  193. }
  194. else
  195. {
  196. Host = RawUrl.Substring(0, index1);
  197. Port = Int32.Parse(RawUrl.Substring(index1 + 1, index2 - index1 - 1));
  198. }
  199. Url = RawUrl.Substring(index2);
  200. }
  201. }
  202. Console.WriteLine("Host is:{0}, Port is:{1}, Url is:{2}", Host, Port, Url);
  203. IPAddress[] address = null;
  204. try
  205. {
  206. IPHostEntry IPHost = Dns.GetHostEntry(Host);
  207. address = IPHost.AddressList;
  208. Console.WriteLine("Web服务器IP地址: " + address[0]);
  209. }
  210. catch( Exception e )
  211. {
  212. Console.WriteLine( "解析服务器地址异常: " + e.Message );
  213. }
  214. Socket IPsocket = null;
  215. try
  216. {
  217. // 连接到真实WEB服务器
  218. IPEndPoint ipEndpoint = new IPEndPoint(address[0], Port);
  219. IPsocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  220. IPsocket.Connect(ipEndpoint);
  221. // 对WEB服务器端传送HTTP请求命令,将原始HTTP请求中HTTP PROXY部分包装去掉
  222. string ReqData = ClientMsg;
  223. // 改写头中的URL,从http://www.test.com/index.php改为/index.php
  224. ReqData = ReqData.Replace("http://"+RawUrl, Url);
  225. // 按照rn切分HTTP头
  226. string[] ReqArray = ReqData.Split(new string[1]{"rn"}, StringSplitOptions.None);
  227. ReqData = "";
  228. // 改写Keep-Alive等字段
  229. for (int index = 0; index < ReqArray.Length; index++)
  230. {
  231. /*
  232. if (ReqArray[index].StartsWith("Accept-Encoding:"))
  233. {
  234. ReqArray[index] = "Accept-Encoding: deflate";
  235. }
  236. */
  237. if (ReqArray[index].StartsWith("Proxy-Connection:"))
  238. {
  239. ReqArray[index] = ReqArray[index].Replace("Proxy-Connection:", "Connection:");
  240. //ReqArray[index] = "Connection: close";
  241. }
  242. /*
  243. else if (ReqArray[index].StartsWith("Keep-Alive:"))
  244. {
  245. ReqArray[index] = "";
  246. }
  247. */
  248. // 修改后的字段组合成请求
  249. if( ReqArray[index] != "" )
  250. {
  251. ReqData = ReqData + ReqArray[index] + "rn";
  252. }
  253. }
  254. ReqData = ReqData.Trim();
  255. byte[] SendBuff = ASCII.GetBytes(ReqData);
  256. IPsocket.Send(SendBuff);
  257. }
  258. catch( Exception e )
  259. {
  260. Console.WriteLine( "发送请求到服务器异常: " + e.Message );
  261. }
  262. // 使用Poll来判断完成,某些站点会出问题
  263. while (true)
  264. {
  265. Byte[] RecvBuff = new byte[1024 * 20];
  266. try
  267. {
  268. if( !IPsocket.Poll( 15 * 1000 * 1000, SelectMode.SelectRead ) )
  269. {
  270. Console.WriteLine("HTTP超时,关闭连接");
  271. break;
  272. }
  273. }
  274. catch (Exception e)
  275. {
  276. Console.WriteLine("Poll: " + e.Message);
  277. break;
  278. }
  279. int Length = 0;
  280. try
  281. {
  282. Length = IPsocket.Receive(RecvBuff);
  283. if (0 == Length)
  284. {
  285. Console.WriteLine("服务端关闭");
  286. break;
  287. }
  288. Console.WriteLine("从服务端收到{0}字节", Length);
  289. }
  290. catch (Exception e)
  291. {
  292. Console.WriteLine("Recv: " + e.Message);
  293. break;
  294. }
  295. try
  296. {
  297. Length = ClientSocket.Send(RecvBuff, Length, 0);
  298. Console.WriteLine("发送{0}字节到客户端", Length);
  299. }
  300. catch (Exception e)
  301. {
  302. Console.WriteLine("Send: " + e.Message);
  303. }
  304. }
  305. /*
  306. // 根据接收字节数来判断完成,某些站点会出问题
  307. try
  308. {
  309. while (true)
  310. {
  311. Byte[] RecvBuff = new byte[1024 * 10];
  312. int Length = IPsocket.Receive(RecvBuff);
  313. if (Length <= 0)
  314. {
  315. Console.WriteLine("从服务端接收数据完成");
  316. break;
  317. }
  318. Console.WriteLine("从服务端收到{0}字节", Length);
  319. Length = ClientSocket.Send(RecvBuff, Length, 0);
  320. Console.WriteLine("发送{0}字节到客户端", Length);
  321. }
  322. }
  323. catch (Exception e)
  324. {
  325. Console.WriteLine(e.Message);
  326. }
  327. */
  328. try
  329. {
  330. ClientSocket.Shutdown(SocketShutdown.Both);
  331. ClientSocket.Close();
  332. IPsocket.Shutdown(SocketShutdown.Both);
  333. IPsocket.Close();
  334. }
  335. catch (Exception e)
  336. {
  337. //Console.WriteLine(e.Message);
  338. }
  339. }
  340. /// <summary>
  341. /// 在客户端和服务器之间中转数据
  342. /// </summary>
  343. /// <param name="client">客户端socket</param>
  344. /// <param name="server">服务端socket</param>