接上篇,這次繼續將程序完善,為其添加自動設置和取消代理的功能 ,主要用到一個API:InternetSetOption。從名字就知道他是干什么的了:設置互聯網選項用的,HTTP代理屬於互聯網的范圍,自然設置代理選項就要用到他了。
- HTTP代理實現請求報文的攔截與篡改1--開篇
- HTTP代理實現請求報文的攔截與篡改2--功能介紹+源碼下載
- HTTP代理實現請求報文的攔截與篡改3--代碼分析開始
- HTTP代理實現請求報文的攔截與篡改4--從客戶端讀取請求報文並封裝
- HTTP代理實現請求報文的攔截與篡改5--將請求報文轉發至目標服務器
- HTTP代理實現請求報文的攔截與篡改6--從目標服務器接收響應報文並封裝
- HTTP代理實現請求報文的攔截與篡改7--將接收到的響應報文返回給客戶端
- HTTP代理實現請求報文的攔截與篡改8--自動設置及取消代理+源碼下載
- HTTP代理實現請求報文的攔截與篡改8補--自動設置及取消ADSL拔號連接代理+源碼下載
- HTTP代理實現請求報文的攔截與篡改9--實現篡改功能后的演示+源碼下載
- HTTP代理實現請求報文的攔截與篡改10--大結局 篡改部分的代碼分析
我們來看一下他的定義
BOOL InternetSetOption( _In_ HINTERNET hInternet, _In_ DWORD dwOption, _In_ LPVOID lpBuffer, _In_ DWORD dwBufferLength ); Parameters 參數 hInternet [in] Handle on which to set information. 想設置信息的那個句柄 dwOption [in] Internet option to be set. This can be one of the Option Flags values. 要設置的網絡選項,可以是一個或者多個Option Flags 的值 lpBuffer [in] Pointer to a buffer that contains the option setting. 包含選項設置的緩存的指針。 dwBufferLength [in] Size of the lpBuffer buffer. If lpBuffer contains a string, the size is in TCHARs. If lpBuffer contains anything other than a string, the size is in bytes. lpBuffer緩存的大小, 如果lpBuffer包含一個字符串,這個大小就是字符串的大小,假如lpBuffer除了字符串還包括一些其它東西,這個大小就是byte 的大小 Return value 返回值 Returns TRUE if successful, or FALSE otherwise. To get a specific error message, call GetLastError. 如果成功了返回TRUE,否則FALSE。如果想得到是什么錯誤,調用 GetLastError 。
官方套話:本人翻譯水平有限,其中錯誤在所難免,敬請原諒,此話后面照樣適用,不再重復說明, OVER
從這些參數的定義可知,第一個參數是要設置信息的程序句柄,第二個是要設置的網絡選項的類型(Option flags),可以是一個也可以多個。
下面我們再來看看Option Flags有哪些:
http://msdn.microsoft.com/zh-cn/library/aa385328(v=vs.85).aspx
這個網址列出了所有的Option flags的網址,N多,對我們有用的,就兩個。其它的自己去看
一.INTERNET_OPTION_PER_CONNECTION_OPTION 75
Sets or retrieves an INTERNET_PER_CONN_OPTION_LIST structure that specifies a list of options for a particular connection. This is used by InternetQueryOption and InternetSetOption. This option is only valid in Internet Explorer 5 and later.
為一個特定的連接設置或者恢復一個INTERNET_PER_CONN_OPTION_LIST 的列表結構。一般被使用於InternetQueryOption 和 InternetSetOption 兩個API。IE5.0及以上才支持這個選項。
Note INTERNET_OPTION_PER_CONNECTION_OPTION causes the settings to be changed on a system-wide basis when a NULLhandle is used in the call to InternetSetOption. To refresh the global proxy settings, you must call InternetSetOption with theINTERNET_OPTION_REFRESH option flag.
注 : 當 調用InternetSetOption.時,如果第二個參數設置成了此值,而第一個參數為NULL時,所引起的變更將是全局性的也就是系統級別的。刷新全局代理設置,你必須調用InternetSetOption,並使用INTERNET_OPTION_REFRESH
Note To change proxy information for the entire process without affecting the global settings in Internet Explorer 5 and later, use this option on the handle that is returned from InternetOpen. The following code example changes the proxy for the whole process even though the HINTERNET handle is closed and is not used by any requests.
注 : 如果想在IE5.0及以上瀏覽器上改變代理設置但又不想影響全局,那么設置句柄為InternetOpen返回的值...... 后面的不翻了.......
For more information and code examples, see KB article 226473
從上面我們知道了。
InternetSetOption(
NULL,
INTERNET_OPTION_PER_CONNECTION_OPTION,
連接選項(一個INTERNET_PER_CONN_OPTION_LIST類型的列表結構),
第三個參數的大小
);
執行上面的API后,我們就可以根據第三個參數里的選項(一個或者多個)來變更互聯網的設置了,而且這些變更是全局性的, 因為第一個參數是NULL,如果第一個參數換成 InternetOpen API返回的句柄的話, 那么第三個參數里的選項, 就是僅針對IE5.0及以上的瀏覽器了 。。
看到這里,其實大家應該已經很清楚了, INTERNET_PER_CONN_OPTION_LIST結構 是一個關鍵,因為這個結構就是用來存放要變更的選項的內容的。下面我們再來看看這個結構的定義 。
typedef struct { DWORD dwSize; LPTSTR pszConnection; DWORD dwOptionCount; DWORD dwOptionError; LPINTERNET_PER_CONN_OPTION pOptions; } INTERNET_PER_CONN_OPTION_LIST, *LPINTERNET_PER_CONN_OPTION_LIST; Members 成員 dwSize Size of the structure, in bytes. 結構的大小 pszConnection Pointer to a string that contains the name of the RAS connection or NULL, which indicates the default or LAN connection, to set or query options on. 一個NULL值或者一個字符串指針,這個字符串就是你要設置或者查詢選項的那個默認的或者局域網的RAS連接的名字。(我日,這翻譯的還真拗口和難懂,看來漢語中也要引入從句這種語法規則 ) 算了,還是直譯吧,指向一個NULL或者字符串指針 , 哪個字符串呢?就是RAS連接的名字,那又是個哪個RAS連接呢?默認的或者局域網的連接,再補充一下,就是你要設置或者查詢選項的那個 。 dwOptionCount Number of options to query or set. 要查詢或者設置的選項的個數 dwOptionError Options that failed, if an error occurs. 選項那個失敗的,假如一個錯誤發生(逐詞直譯) pOptions Pointer to an array of INTERNET_PER_CONN_OPTION structures containing the options to query or set. 一個包含查詢或設置選項的INTERNET_PER_CONN_OPTION結構的數組的指針
還是來看一個例子吧 。例子的力量是偉大的。
INTERNET_PER_CONN_OPTION_LIST list ; INTERNET_PER_CONN_OPTION options[5]; // 有幾個選項需要設置這里就分配幾個 list.pszConnection = NULL; // 為NULL,表示默認的連接 list.dwOptionCount = 5 ; // options的個數 list.dwOptionError = 0 ; list.pOptions = options; // 指向options,就是選項的數組
這里又出來一個新結構INTERNET_PER_CONN_OPTION , 再來看一看其定義
typedef struct { DWORD dwOption; union { DWORD dwValue; LPTSTR pszValue; FILETIME ftValue; } Value; } INTERNET_PER_CONN_OPTION, *LPINTERNET_PER_CONN_OPTION;
只有2個成員,一個DWORD類型的dwOption,另外一個union類型的value ; 看看其說明
Members 成員 dwOption Option to be queried or set. This member can be one of the following values. 被查詢或者設置的選項,這個成員可以下面的值當中的一個。 下面很多值,這里就不列出來了。后面說明的時候會對用到的進行說明 Value Union that contains the value for the option. It can be any one of the following types depending on the value of dwOption: 選項的值,可以下面所列類型中的一個,至於是那個,這個要依賴你的dwOption的值 dwValue Unsigned long integer value. 無符號長整型 pszValue Pointer to a string value. 指向一個字符串的指針 ftValue A FILETIME structure. 一個FILETIME結構
根據上面的描述,第一個成員是要設置的選項的類型,第二個是要設置的選項的值,至於這個值是什么類型,要依據第一個選項的類型的來決定。繼續例子
INTERNET_PER_CONN_OPTION options[5] ; options[0].dwOption = INTERNET_PER_CONN_FLAGS ; options[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY; options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER ; options[1].Value.pszValue = _T("http=127.0.0.1:8888;"); options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS ; options[2].Value.pszValue = _T("<-loopback>;"); options[3].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL ; options[3].Value.pszValue = NULL; options[4].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS; options[4].Value.dwValue = 0 ;
舉這個例子的原因是因為這五個選項正好是我們設置代理服務器要用到的選項,現在一個一個的來進行說明。
1. INTERNET_PER_CONN_FLAGS
Sets or retrieves the connection type. The Valuemember will contain one or more of the following values: 設置或者恢復連接類型,值成員將包含一個或者多個如下的值: PROXY_TYPE_DIRECT The connection does not use a proxy server. 連接不使用代理服務器。 PROXY_TYPE_PROXY The connection uses an explicitly set proxy server. 連接使用一個顯式設置的代理服務器 PROXY_TYPE_AUTO_PROXY_URL The connection downloads and processes an automatic configuration script at a specified URL. 在某個指定的URL上此連接下載和處理一個自動配置腳本,沒明白啥意思,不過也用不到 PROXY_TYPE_AUTO_DETECT The connection automatically detects settings. 連接自動檢查設置,這個也用不到
再看我們例子里的設置
options[0].dwOption = INTERNET_PER_CONN_FLAGS ; options[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY;
設置成 PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY 這個代表啟用代理.
也就是下圖的那個勾就勾上了
如果只設置成PROXY_TYPE_DIRECT
options[0].Value.dwValue = PROXY_TYPE_DIRECT ;
則表示取消代理。
也就是上圖的勾沒了 。
2. INTERNET_PER_CONN_PROXY_SERVER
Sets or retrieves a string containing the proxy servers.
設置或恢復包含代理服務器信息的字符串
也看例子程序里的設置
options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER ; options[1].Value.pszValue = _T("http=127.0.0.1:8888;");
因為是個字符串,所以是pszValue ,內容的格式如下:
“http=127.0.0.1:8888 ; “ 也可以是 “http=127.0.0.1:8888 ;https=192.168.1.88:8080 ;”
能看出來規律嗎。就是用分號分開不同協議的代理設置,用等號分開協議名和代理服務器的地址和端口。
那么類推一下,如果還需要再設置個FTP協議的代理服務器,代理服務器的地址是本機,端口是8888,那么應該怎么設置呢.
“http=127.0.0.1:8888 ;https=192.168.1.88:8080 ;ftp=127.0.0.1:8888;”
http=127.0.0.1:8888 ;
http=127.0.0.1:8888 ;https=192.168.1.88:8080 ;
http=127.0.0.1:8888 ;https=192.168.1.88:8080 ;ftp=127.0.0.1:8888;
看明白了吧,如果U自認還是正常人類的話。那么我們繼續往下了。
3. INTERNET_PER_CONN_PROXY_BYPASS
一樣的先看定義
Sets or retrieves a string containing the URLs that do not use the proxy server. 設置或者恢復不使用代理的網址。
看他的命名就基本能猜出來他是干什么的了,就是設置一些網址直接訪問網絡,而不使用代理。這個選項是很有用處的,例如,你想使用代理訪問國外網站,但是使用了代理后,很多的國內網站又訪問不了,怎么辦呢,很簡單,把網內的網站加到這個例外情況(忽略列表)里就可以了。
同樣的,再看看我們例子里的設置
options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS ; options[2].Value.pszValue = _T("<-loopback>;");
至於loopback是什么,可自行GOOGLE或BAIDU。
后面還有兩個選項就不詳細講了,一個設NULL,一個設0就可以了。
前面的代碼綜合起來就是
INTERNET_PER_CONN_OPTION_LIST list ; INTERNET_PER_CONN_OPTION options[5]; // 有幾個選項需要設置這里就分配幾個 list.pszConnection = NULL; // 為NULL,表示默認的連接 list.dwOptionCount = 5 ; // options的個數 list.dwOptionError = 0 ; list.pOptions = options; // 指向options,就是選項的數組
options[0].dwOption = INTERNET_PER_CONN_FLAGS ; options[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY; options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER ; options[1].Value.pszValue = _T("http=127.0.0.1:8888;"); options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS ; options[2].Value.pszValue = _T("<-loopback>;"); options[3].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL ; options[3].Value.pszValue = NULL; options[4].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS; options[4].Value.dwValue = 0 ; InternetSetOption( NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &options, sizeof(options) );
二. INTERNET_OPTION_PROXY_SETTINGS_CHANGED 95
Alerts the current WinInet instance that proxy settings have changed and that they must update with the new settings. To alert all available WinInet instances, set the Buffer parameter of InternetSetOption to NULL and BufferLength to 0 when passing this option. This option can be set on the handle returned by InternetConnect or HttpOpenRequest.
通知當前的WinInet實例代理設置已經被改變了,必須去更新他的代理設置,如果要通知所有有效的WinInet實例的話,第三個參數設成NULL,最后一個參數設成0 。
我們當然是想通知所有的WinInet實例了,所以這里后面兩個參數設成NULL和0就行了。
InternetSetOption( NULL, INTERNET_OPTION_PROXY_SETTINGS_CHANGED, NULL, 0 );
這一步重要,使用INTERNET_OPTION_PER_CONNECTION_OPTION后,設置其實已經改變了,但是還不能立即生效,需要重啟后才能生效,但如果想立即生效,就要用到INTERNET_OPTION_PROXY_SETTINGS_CHANGED了。
好了,再和前面的代碼綜合一下,另外了加了點注釋 。
INTERNET_PER_CONN_OPTION options[5] ; options[0].dwOption = INTERNET_PER_CONN_FLAGS ; options[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY; options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER ; options[1].Value.pszValue = _T("http=127.0.0.1:8888;"); options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS ; options[2].Value.pszValue = _T("<-loopback>;"); options[3].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL ; options[3].Value.pszValue = NULL; options[4].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS; options[4].Value.dwValue = 0 ; // 改變設置 InternetSetOption( NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &options, sizeof(options) ); // 通知所有的WINET實例,設置已經改變了,請立即更新代理設置 InternetSetOption( NULL, INTERNET_OPTION_PROXY_SETTINGS_CHANGED, NULL, 0 );
在C#中調用API,尤其涉及到過多結構體的時候,非常的麻煩,所以為了方便,前面的例子我使用了C++來寫。至於C#中如何實現這些功能,各位可自行看源代碼,原理都是一樣的,代碼見 附錄 -WinINetProxy.cs 文件 - SetToWinINET 方法.
為了實現啟動的時候自動設置代理,退出的時候自動退出代理的功能,我們需要在FrmMain.cs 做兩處變動 。
第一處FrmMain構造函數的最后。
原來是
proxy.Start(Config.ListenPort);
現在變更為
if (proxy.Attach()) // 自動設置代理成功后 { proxy.Start(Config.ListenPort); // 啟動代理服務器 }
第二處FrmMain_FormClosed事件的代碼里
原來是
if (proxy != null) { proxy.Stop(); }
變更為
if (proxy != null) { proxy.Stop(); proxy.Detach(); }
從上面可以看到在程序啟動的時候,我們增加了一個proxy.Attach(),而在程序退出時又增加了一個proxy.Detach()。Attach是自動設置代理,Detach是取消代理。這兩個方法全部是增加在Proxy類里的
1 internal bool Attach() 2 { 3 if (!this.IsAttached) 4 { 5 return this.winINetProxy.SetToWinINET("DefaultLAN"); 6 } 7 return true; 8 } 9 10 internal bool Detach() 11 { 12 if (this.IsAttached) 13 { 14 return this.winINetProxy.SetToWinINET("DefaultLAN"); 15 } 16 return true; 17 }
這兩個方法都是調用了winINetProxy.SetToWinNet方法。winINetProxy 是WinInetPorxy類的一個實例,WinInetProxy類就是前面提到的設置代理和取消代理的實現類 。