MFC + Wininet + 代理认证 = 问题

Posted

技术标签:

【中文标题】MFC + Wininet + 代理认证 = 问题【英文标题】:MFC + Wininet + proxy authentication = problems 【发布时间】:2011-07-28 16:15:10 【问题描述】:

我有一些代码(它实际上是用于通过网络界面发送 SMS 消息,但这不相关)。该代码在代理服务器不存在 的情况下工作正常,但一位客户希望使用此配置。我一直在使用我们的代理进行测试,但无法正常工作。在翻阅帮助时,我发现了 MSKB 文章 195650(如何使用 WinInet 处理代理授权),其中包含这颗智慧之珠:

There are several ways to handle HTTP_STATUS_PROXY_AUTH_REQ without
displaying a user interface. By far the easiest way to do this is by 
using the InternetSetOption function with the flags
INTERNET_OPTION_PROXY_PASSWORD and INTERNET_OPTION_PROXY_USERNAME...

...The same functionality can be accomplished in an MFC application 
by detecting HTTP_STATUS_PROXY_AUTH_REQ, calling 
CHttpConnection::SetOption, then re-calling CHttpFile::SendRequest. 

所以我在我的代码中实现了这个解决方案,从需要身份验证的代理中检测到 407 错误,然后通过 SetOption 调用提供基本身份验证:

     if (AfxParseURL (m_csServerUrl, dwServiceType, csServerName, csObjectName, nPort))
     
        CString csProxy = m_pOwner->GetProxyServerSetting();
        if (csProxy.GetLength() > 0)
        
           pSession  = new CMyInternetSession (TEXT("SmGen"),
                                               1,
                                               INTERNET_OPEN_TYPE_PROXY,
                                               csProxy,
                                               NULL,
                                               INTERNET_FLAG_KEEP_CONNECTION);
        
        else
        
           pSession  = new CMyInternetSession (TEXT("SmGen"),
                                               1,
                                               INTERNET_OPEN_TYPE_PRECONFIG,
                                               NULL,
                                               NULL,
                                               0);
        
        if (pSession)
        
           pSession->SetOwnerDialog (m_pOwner);
           pHttpConn = pSession->GetHttpConnection (csServerName, (INTERNET_PORT)nPort, NULL, NULL);

           if (pHttpConn)
           
              dwFlags = INTERNET_FLAG_RELOAD | 
                        INTERNET_FLAG_DONT_CACHE;    
              pHttpFile = pHttpConn->OpenRequest (CHttpConnection::HTTP_VERB_GET, 
                                                  csObjectName + TEXT("?") + csHTTP,
                                                  NULL,
                                                  1,
                                                  NULL,
                                                  NULL, 
                                                  dwFlags);
              if (pHttpFile)
              
                 pHttpFile->AddRequestHeaders (csHeaders);

                 if (pHttpFile->SendRequest ())
                 
                    pHttpFile->QueryInfoStatusCode (dwResult);
                    bRetryWithAuth = FALSE;

                    switch (dwResult)
                    
                       case HTTP_STATUS_OK:
                          // log success
                          break;

                       case HTTP_STATUS_PROXY_AUTH_REQ:
                          bRetryWithAuth = TRUE;
                          break;

                       default:
                          // log failure
                          break;
                    

                    if (bRetryWithAuth)
                    
                       csProxyUsr = m_pOwner->GetProxyUsername();
                       csProxyPwd = m_pOwner->GetProxyPassword();

                       pHttpConn->SetOption (INTERNET_OPTION_PROXY_USERNAME,
                                             csProxyUsr.GetBuffer(1),
                                             csProxyUsr.GetLength());
                       csProxyUsr.ReleaseBuffer();

                       pHttpConn->SetOption (INTERNET_OPTION_PROXY_PASSWORD,
                                             csProxyPwd.GetBuffer(1),
                                             csProxyPwd.GetLength());
                       csProxyPwd.ReleaseBuffer();

                       if (pHttpFile->SendRequest ())
                       
                          // ... TIMEOUT

现在解决问题。问题是第二个 SendRequest 没有失败或抛出另一个错误,它只是 超时。过了一会儿,我通过我的包装处理程序抛出了 CInternetException 12002(超时)。这有点烦人。不用说,短信永远不会到达。

代理服务器地址的格式为 a.b.c.d:8080,以消除 DNS 作为致病因素。我的 MIS 部门向我保证,我提供的用户名和密码是有效的(如果我传递了错误的 uid/pwd,它只会恢复为 407 错误,所以我知道他们至少可以访问代理)。

我在这里和网上都能找到所有东西,但我一无所获。遗憾的是,如果您只有一个代理,那么简单地使用 INTERNET_OPEN_TYPE_PRECONFIG 并希望系统能够自动获取它需要的所有内容是行不通的。

请记住,代码的非代理相关功能没有任何问题,因为如果我通过擦除提供服务器 ip:port 的注册表项来消除代理,它就会重新焕发生机。

我完全被难住了。有没有人见过这个?考虑到关于代理身份验证的查询数量没有回复,我不抱希望......

编辑:

我已将此代码转换为使用 WinHttp,因为有一个涵盖代理(带身份验证)的 MS 示例,并且无论如何不推荐使用 WinInet。现在一切正常。

【问题讨论】:

如果你知道你正在访问代理......也许用 Wireshark 窥探会产生一些有用的信息?或者也许是提琴手? 【参考方案1】:

通常在客户端收到响应后会关闭 HTTP 连接。 因此,当您发送带有身份验证详细信息的第二个请求时,代理将关闭连接(或至少停止接收)。

所以你必须再次调用 OpenRequest 来建立一个新的连接。

【讨论】:

这与 MSDN 文档说我只需要重新发送相矛盾,但它很容易尝试,所以我会在明天早上试一试并报告。 HTTP 连接在收到响应时关闭。如果这是真的,那会让网络通信变得慢很多。

以上是关于MFC + Wininet + 代理认证 = 问题的主要内容,如果未能解决你的问题,请参考以下文章

MFC wininet CHttpConnection 线程安全吗?

使用 wininet 设置代理设置

WinInet如何使用sock代理

如何使用 WinINet 通过代理连接到 HTTPS

无法从 WinForms 应用程序设置 WinInet 代理

Vbscript 获取 WinINET API 的代理配置