编写了一个HTTP高匿代理

原创 2010年09月24日 18:56:00

本以为编写http代理和上一篇的端口转发差不多的,结果实际一编写起来发现要复杂的多。怎么回事呢,就在于要手动解析http协议。

说简单点吧,如果直接用ie上一个网站,用sniffe一看http请求头是这样的。

 

GET / HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/QVOD, application/QVOD, */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
Accept-Encoding: gzip, deflate
Host: www.microsoft.com
Connection: Keep-Alive
Cookie: xxxxxxxxx

 

 

但是如果用代理就变成了这样

 

GET http://www.microsoft.com/ HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/QVOD, application/QVOD, */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
Accept-Encoding: gzip, deflate
Host: www.microsoft.com
Proxy-Connection: Keep-Alive
Cookie: xxxxxxxxx

 

区别就在这里,用代理get那地方会把完整url写上,而且Connection加上了proxy标志,其他一样。所以用TcpListener和TcpClient每接受一个连接,就要首先把提交的http请求的头部分改写,就是把下面的改成上面的。

这是GET方法,只有请求的头部分没有实体部分。

还有一种POST方法,是包含实体部分的,比如上传图片了什么的,都是用的POST方法。post方法紧跟在头部分后面。

怎么判断哪是头那是实体呢?

http协议规定头必然有2个连续的"/r/n",就像上面Cookie后面就跟了2个/r/n,所以读取请求头的时候只要读到/r/n/r/n,那么前面就是头,后面就是实体。实体大小在上面有一个Content-Length标记。所以从/r/n/r/n后面读Content-Length大小后就结束了

 

还有一种是CONNECT方法,凡是用connect的就是ssl加密通信,当收到 CONNECT urs.microsoft.com:443 HTTP /1.0之类的请求后,代理服务器要给客户(如IE)返回一个"HTTP/1.1 200 Connection established/r/n/r/n",然后就tcpclinet一个服务器的443,后只负责客户和服务器的转发就可以了,就像上一篇的转发一样,什么都不用管了。这种反而最简单。

就以上3种最常用。

 

其他的请求方法还有put option什么的,因为实在是没见过,也不知道去哪里试所以都按照get post的方法处理了。

 

服务器返回更麻烦,麻烦就在于http协议过于宽松,如果每个回应或者请求都包括Content-Length或者chunked之类表明实体大小的东西那么就好判断了,http协议规定判断实体大小的方法有好几种,当然最准确的就是有Content-Length和chunked,还有以服务器断开连接来判断的,有的回应中没有Content-Length或者chunked,以什么时候断开来判断,疑似那些网络上下坏文件的就是这么造成的,客户根本不知道有多大,如果读取完了服务器断开那么没问题,如果读着读着网络中断了了,客户还以为是服务器断开了是吧。

 

所以读取服务器回应的时候就要判断好几个值

1、判断状态码,http协议规定1xx 204 304肯定不包括实体,所以读到/r/n/r/n就不用再读了

2、判断没有Content-Length

3、判断有没有chunked

 

如果有Content-Length,那么读取和上面请求头一样,/r/n/r/n后面读Content-Length个返回给客户。

还有一种是chunked编码,这种编码一般是gzip压缩的,微软论坛就是用的这种,当你请求页面的时候,服务器一边把页面gzip压缩一点传给你再压缩一点传给你,所以开始没法得到Content-Length,但是每chunked却有标记的大小

 

 

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 2.0
X-AspNet-Version: 4.0.30319
Set-Cookie: Set-Cookie: X-Powered-By: ASP.NET
P3P: CP=ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI
Server: CO1VB06
Date: Fri, 24 Sep 2010 09:33:28 GMT
ntCoent-Length: 166137
Content-Encoding: gzip
Transfer-Encoding: chunked

 

2D23
...........}.s.G.......j....*u......y....%...;QO.M.[....3..,...
.!..O....H-."v..>.............=YY

 

像上面chunked/r/n/r/n后面是实体,第一行2D23就是一chunk的大小所以在2D23/r/n后面开始读2D23个然后会紧跟着/r/n,然后后面就是下一chunk的大小,直到最后一chunk是0大小。实体结束,最后再来一个/r/n。也就是说chunked的最后7个一定是/r/n0/r/n/r/n,本来判断读到/r/n0/r/n/r/n就结束应该没问题,但是为了保险起见,还是一次一次的读大小再读大小。

 

 

最讨厌的就是既没有content-length也没有chunked,如果返回的是conntion: close还好点,读着读着发现那边断开了就行了,如果返回的是keep-alive,networkstream.read那里就卡住了,表现在ie就是看似页面都加载完了,但是进度条还是在慢慢地走着,所以只能加个读取超时,比如3秒钟还读不出来就断开连接。反而ie那里却显示“完成”,

 

而且如果再分析keep-alive那就太麻烦了,我是从服务器那里一旦读取完,不管是不是keep-alive一律关闭连接,也就说ie每一个请求都单独的tcpclient一次服务器完后关闭。

 

但是处理ie就不能这样了,ie每开一的端口和代理服务器连接发送的请求是一个或多个,所以tcplistener每进来一个ie的tcpclient(即ip+端口),处理完这个tcpclient的请求后不能像断开服务器那样断开,否则ie就什么都不显示一直走那个进度条或者找不到服务器。所以处理完一次请求后要循环再读这个tcpclient的下一个请求,如果发现这个请求断开了就彻底关闭这个tcpclient。所以整个流程是这样的

 

 

1、tcplistener监听

2、循环tcplistener.accepttcpclient()

3、进来一个tcpclient()后,启动一个线程处理,上面继续循环等待

 

4、同时3的那个tcpclient开始处理,读取他的http请求头,改写http请求头,然后把改写的请求头和下面实体部分发送到请求的服务器,这里要注意必须是随读随改随发送,不能等到全读取完了再发送,否则就超时了。

5、发送完毕,开始从服务器接收

6、和第4差不多,也是从服务器随读随往ie发送,也是不能读取完再发否则就超时

7、读取完毕,断开和服务器的连接,不管是不是keep-alive

8、重复到第4步开始,再从ie读取下一个请求,如果有那么再执行5/6/7/8,直到发现ie的这个tcpclient断开了,就彻底结束掉这个线程。

 

 

大体就是这个样子了,所以把上面的条件用代码写出来就是http代理服务器了,把上面这些条件用代码写出来是很麻烦的,所以写出来的代码是非常丑陋的,而且我本来写的代码就很难看,这样一来就更没法看了,所以我就不献丑了,关键是解释这个大体过程比代码要重要,当时我找这过程别人的文章解释的都不太清楚,看了几页http协议文档,应该是机器翻译的,很难看懂,总共100几十页,看全了不值当的,有一篇介绍c#2003做代理的文章,一看根本就是端口转发没改请求头都,一试果然不行,还有一个外国人的,很长不愿看了,那种风格就像反编译.net类库看到的那种感觉,坐一块右一块的,而且运行后发现也不大行,所以只好一点一点的抠,但是好在编译后运行效果还是挺好的,测试了一下午,cpu占用率没超过3%的时候,内存占用10兆左右,下载什么的ssl都可以,只是上网偶尔出现进度条等待的情况,就是上面说的因为服务器那边没有实体长度信息等待超时的情况,但是这无关大雅了。大部分网站都是和直接用ie一样刷的就出来了,感觉不到慢。下载更没问题了,和直接用ie下载速度一样的。

 

最后就是为什么说是高匿呢,本来想查查原理的代理部分,又想先试试是什么结果,本以为是透明代理,结果试了好几个检查代理匿名性的网站,结果全都说是“高匿”,关于匿名性,代码体现的仅仅是把ie发送的proxy-connection改成了connection,难道这样就“高匿”了?那就高匿吧。对了,picasaweb本来是打不开的,我用了我这个代理后就能打开了。

 

大家有兴趣的可以下载来测试一下

相关文章推荐

.net 真实代理和透明代理的交互

1.本地代理调用using System;using System.Runtime.Remoting ;using System.Runtime.Remoting.Services ;using Sy...
  • anghlq
  • anghlq
  • 2007年07月17日 20:02
  • 3979

透明代理、匿名代理、混淆代理、高匿代理有什么区别?

这4种代理,主要是在代理服务器端的配置不同,导致其向目标地址发送请求时,REMOTE_ADDR, HTTP_VIA,HTTP_X_FORWARDED_FOR三个变量不同。 1、透明代理(Tran...

搭建自己的http代理服务器

由于在某些特定场景下,我们的外网访问会受到限制,如果有一些访问需求的话就需要一个代理作为中转了。 首先需要一台机器作为中转的服务器,这时候当然要去阿里云买一台啦。操作系统一定要选Linux,我使用的是...
  • fylfyl2
  • fylfyl2
  • 2015年09月16日 16:56
  • 16669

用node-http-proxy搭建谷歌代理

程序员三大必备网站是:Google、Github、StackOverflow。如果你还在用Baidu搜索技术文章的话,我想说的是,少年你已经被鄙视很多年了,赶紧换成谷歌吧,不要再被鄙视了!Github...
  • Jaye100
  • Jaye100
  • 2015年11月29日 21:28
  • 4640

简易HTTP代理的实现

编写一个简易的HTTP代理服务器,步骤其实很简单:      1.设置一个监听套接字gListen_Socket;      2.每当接受到客户端的请求之后,我们构建一个新的线程来继续监听客户端的请求...

将socks代理转换成http代理

今天遇到一个很蛋疼的需求,我有一个在国外的服务器,通过ssh -D可以在本地生成一个socks5代理,但是我要访问一个国外的bt网站,它的tracker连接都只能用代理,而且它只能使用http代理,因...

HTTP代理协议

HTTP代理的几种方式 Wireshark抓包 第一种方法:直接请求 普通的提交HTTP请求的过程: 直接连接远程服务器后,当连接成功时向服务器提交HTTP头,注意看看大概的格式 GET /...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

【原创】 shell一键配置squid高匿代理服务器 high-Anonymity

http://blog.sina.com.cn/s/blog_83dc494d0102wcr5.html 转载▼ 标签:  squid   high-anon...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:编写了一个HTTP高匿代理
举报原因:
原因补充:

(最多只允许输入30个字)