zoukankan      html  css  js  c++  java
  • 编写了一个HTTP高匿代理

    http://blog.csdn.net/laotse/article/details/5903651

    本以为编写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本来是打不开的,我用了我这个代理后就能打开了。

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

  • 相关阅读:
    IO 单个文件的多线程拷贝
    day30 进程 同步 异步 阻塞 非阻塞 并发 并行 创建进程 守护进程 僵尸进程与孤儿进程 互斥锁
    day31 进程间通讯,线程
    d29天 上传电影练习 UDP使用 ScketServer模块
    d28 scoket套接字 struct模块
    d27网络编程
    d24 反射,元类
    d23 多态,oop中常用的内置函数 类中常用内置函数
    d22 封装 property装饰器 接口 抽象类 鸭子类型
    d21天 继承
  • 原文地址:https://www.cnblogs.com/adodo1/p/4327408.html
Copyright © 2011-2022 走看看