HTTP代理实现请求报文的拦截与篡改8--自动设置及取消代理
接上篇,这次继续将程序完善,为其添加自动设置和取消代理的功能 ,主要用到一个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类就是前面提到的设置代理和取消代理的实现类 。