我們學校的網絡環境是,所有的出校通路均須通過代理伺服器(我們叫作sproxy),并且不能連接配接國外網站。為此,我想要做一個自動抓取和驗證出國代理清單的WebService。我們所有的驗證代理的請求,都需要通過兩層代理,最終通到用來測試的網站(我使用了www.redhat.com)
技術上的思路是,通過連接配接第一層代理sproxy(支援http tunnel),給第二層代理發送GET指令,進而完成對目标網頁的通路。
首先,通過普通的匿名透明代理的方法,是直接使用Socket發送GET指令,隻不過與GET普通網站稍有不同罷了
直接通路:
GET / HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint, **
Accept-Language: zh-cn
UA-CPU: x86
Proxy-Connection: Keep-Alive
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
Host: www.redhat.com
Cookie: s_vi=[CS]v1|44AAA05400004577-A170C060000008A[CE]; Apache=61.147.159.196.23241152032846747
可以看出來,隻要給代理伺服器發送正确的請求位址即可,不需要程式上特殊的變化
事實上,對于不使用代理伺服器的場合,你可以直接向某HTTP伺服器發送GET /,而這在使用代理時不行,需要說明詳細位址或者标明Host: www.redhat.com
對于兩層代理,就有所不同了
首先要給sproxy發送一個CONNECT指令,令其去連接配接某公衆網代理(這裡稱為proxy2),然後通過這個Socket連接配接發送GET指令給proxy2。這裡對sproxy(以後稱proxy1)有一個要求,即必須支援CONNECT,也就是說必須是一個HTTP Tunnel,或者說支援HTTPS。
由于一開始設定上的問題,我本以為傳回HTTP 1.0的CCProxy不支援這樣的方式,因為據說HTTP 1.0就不能支援HTTPS,連接配接一次,發送了資料一定會斷開(一開始的測試中的确如此)。後來查閱資料才知道,HTTP 1.0隻是預設不使用Connect: Keep-Alive的參數,事實上也是支援的,隻要置上這個參數即可。
但實際使用中,發現不加上這個參數也是可以的(我對proxy1沒有使用這個參數,可能是.net下的Socket類自動加上了)。
經過連續數天的測試,一直不能通過,問題集中在,給proxy2發送一次CONNECT指令,Socket就會斷開,無法收到資料。這是典型的不支援HTTP Tunnel的表現,但是我一直懷疑是CCproxy和學校代理伺服器的問題,沒有發現根源所在。
因為我們通路sproxy需要使用者名密碼驗證,我不得已才使用了CCProxy。後來,我又安裝了一個代理伺服器軟體,發現其中隻要使用“代理嵌套”(CCproxy叫作二級代理)的時候都需要HTTPS,我突然想到,會不會需要在CCproxy中設定?
于是,在CCproxy的設定-進階-二級代理中把代理類型設為HTTPS,使用proxy1做上級代理,果然成功。事實上,直接連接配接學校代理應該也是可以的,隻是我還不知道如何在代理請求中發送使用者驗證資訊(需要将驗證資訊截斷為多個資料包,不能直接發送)。
-
代碼如下,調試通過并成功運作了十多天了:
背景一個類:
public string Get_Socket_Request_uip(string ip) {
Encoding ASCII = Encoding.Default;
//給Proxy2發送指令
string Con = "CONNECT " + ip + " HTTP/1.1
Connection: Keep-Alive
";
string Get = "HEAD http://www.RedHat.com/ HTTP/1.1
Host: www.RedHat.com
Pragma: no-cache
Connection: Close
";
Byte[] ByteCon = ASCII.GetBytes(Con);
Byte[] ByteGet = ASCII.GetBytes(Get);
Byte[] RecvBytes = new Byte[256];
Byte[] RecvBytes2 = new Byte[256];
string strRetPage = null;
string strRetCon = null;
//建立Socket
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//連接配接本地CCProxy
IPAddress hostip = IPAddress.Parse("127.0.0.1");
IPEndPoint ipend = new IPEndPoint(hostip, 808);
//設定逾時,超過了就證明這個代理無效
s.SendTimeout = 20000;
s.ReceiveTimeout = 20000;
//這句話就是socket的Keep-Alive,我沒有使用
//s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.KeepAlive, true);
s.Connect(ipend);
try
{
//發送CONNECT指令
s.Send(ByteCon, ByteCon.Length, 0);
Int32 bytes = s.Receive(RecvBytes, RecvBytes.Length, 0);
strRetCon = strRetCon + ASCII.GetString(RecvBytes, 0, bytes);
if (strRetCon.Contains("200"))
{
s.Send(ByteGet, ByteGet.Length, 0);
Int32 bytes2 = s.Receive(RecvBytes2, RecvBytes2.Length, 0);
strRetPage = strRetPage + ASCII.GetString(RecvBytes2, 0, bytes2);
//本來這裡會循環接收資料(本來發送的不是HEAD而是GET,取整個頁面),後來發現有時候這會導緻斷線,就去掉了,隻用HEAD指令取傳回的HTTP Head
}
}
catch
{
break;
}
finally
{
s.Shutdown(SocketShutdown.Both);
s.Close();
}
return strRetPage;
}
public void TestProxy() { int r = 0;
try
{
foreach (Match i in matches)
{
string ip = i.Result("${ip}");
string port = i.Result("${port}");
try
{
//這裡的hc是背景那個類,myhttpclient
string result = hc.Get_Socket_Request_uip(ip + ":" + port);
if (result.Contains("200 OK"))
{
checkedproxy[r] = ip + ":" + port;
r = r + 1;
}
}
catch (TimeoutException)
{
continue;
}
catch
{
continue;
}
}
}
catch
{
break;
}
}
WebService代碼就不給出了,就是調用這個方法而已。
大家應該可以看出,這裡實際上通過了三層代理(CCproxy,proxy1,proxy2),是以,如果要通過n層代理,方法也是一樣的,隻要一層層的CONNECT下去就行,但要求前n-1層代理都要支援HTTP tunnel。
另外,在實際部署中使用這個程式,需要在Web.config中添加一行
system.web項下
<httpRuntime executionTimeout=”600″/>