WinINet 测试应用程序
一个用于测试异步 WinINet 功能的 WTL 应用程序。
引言
这是一个 WTL 程序,能够使用几乎所有常见组合的必需项来测试与 Web 服务器的连接。该程序能够连接
- POST 或 GET
- SSL
- 根据 Web 服务器的要求,提供个人证书
- 改变接收超时时间
- 为 Web 服务器提供用户名/密码
- 忽略错误的 CA 证书
该程序使用异步 WinINet 调用来处理所有通信和报告。该程序还演示了如何允许用户从“我的”存储区中选择个人证书。
背景
我编写了这个程序来帮助测试我在遇到不寻常的防火墙配置和 Web 服务器安全要求时遇到的一些问题。WinINet 回调例程的输出在调试连接问题时非常有帮助。我已经将许多消息放置在 ATLTRACE 宏输出以及程序的主对话框中。虽然大多数操作的示例代码可以在各种位置找到,但我需要将所有内容整合到一个单一的应用程序中,并使用一致的错误处理来实现。
该程序最初是来自 Microsoft 的几个示例的组合。经过几个小时的编码和快速移植到 WTL 后,该应用程序几乎被完全删除并重写。我需要为这个应用程序给予应有的荣誉:Microsoft 的示例为我节省了很多时间(除了它们错误或不完整并且花费了我很多时间的时候)。
Using the Code
代码非常易于使用。只需下载演示并编译它即可。我正在使用 WTL 8.0 和 VS 2005。您可能需要下载 WTL。
这是一个 WTL 模式对话框应用程序。我使用由 Andy Smith 开发的一些等待函数来解决使用模式对话框时出现的阻塞问题。您真的需要检查位于 SendRequest
中的主要 WinINet 循环。在这里,您将学习如何处理 ERROR_INTERNET_INVALID_CA
、ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED
和 HTTP_STATUS_DENIED
。WinInetCallback
方法用于提供信息,例如个人证书或用户名和密码,以便重新提交给 Web 服务器,并通过上下文变量将通信错误传递给 SendRequest
中的 WinINet 循环。
获取指向个人证书的指针
有时您可能需要允许用户选择个人证书。例如,您正在编写一个使用 WinINet 的应用程序。以下是一些简单的代码,允许用户从其证书存储区中加载的个人证书列表中进行选择
#include <prsht.h> #include <cryptuiapi.h> ... HCERTSTORE m_hMyStore; PCCERT_CONTEXT m_pHSCertContext; ... // Open the 'MY' certificate store to be presented to the user if(!(m_hMyStore = CertOpenStore( CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, L"MY"))) { // Let the user know we could not open the store. CString tMsg, tTitle; GetErrorMessage(GetLastError(), &tMsg); MessageBox((LPCTSTR) tMsg, (LPCTSTR) tTitle.LoadString(IDS_TITLEFAILED), MB_ICONEXCLAMATION); return FALSE; } else { // If we get to this stage, the certificate store // was opened successfully. // Display a selection of certificates to choose from. m_pHSCertContext = CryptUIDlgSelectCertificateFromStore( m_hMyStore, NULL, NULL, NULL, CRYPTUI_SELECT_EXPIRATION_COLUMN | CRYPTUI_SELECT_LOCATION_COLUMN | CRYPTUI_SELECT_FRIENDLYNAME_COLUMN | CRYPTUI_SELECT_INTENDEDUSE_COLUMN , 0, NULL); } //Show an error if a certificate is not seleted. if (!m_pHSCertContext) { CString tMsg, tTitle; MessageBox((LPCTSTR) tMsg.LoadString(IDS_ERR1), (LPCTSTR) tTitle.LoadString(IDS_ERRT1), MB_ICONEXCLAMATION); return FALSE; }
在 WinINet 句柄上提供个人证书
TCP/IP 通信有点棘手,因为您无法在发出请求时提供证书。您需要调用 OpenRequest
,然后等待您的回调例程中的 INTERNET_STATUS_REQUEST_COMPLETE
信号。然后,您可以检查返回的错误,查找 INTERNET_ASYNC_RESULT
结构中的 ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED
。当您收到错误时,您需要在 OpenRequest
句柄上使用 InternetSetOption
来提供证书,然后重复 OpenRequest
。这真的很简单。只是文档和示例省略了您需要的大量错误检查和恢复工作。拥有一个带有有效 SSL 证书并需要个人证书才能连接的服务器设置会有所帮助。以下是处理 ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED
的代码片段
case INTERNET_STATUS_REQUEST_COMPLETE: { //check for errors if(ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED == pRes->dwError) { // The server requires a certificate // Save dwError so we can act on it. cpMainDlg->m_dwError = pRes->dwError; sMsg.Format("%s: CLIENT_AUTH_CERT_NEEDED (%d)", cpMainDlg->m_szMemo, pRes->dwError); // Check that we have a certificate pointer, // otherwise, it's time to fall over dead. if(cpMainDlg->m_pHSCertContext == NULL) { //write the callback information to the buffer sMsg += _T(" No cert provided by user"); } else { // We have a pointer (there's no guarantee it's valid) // We have to be careful and check all returns for errors // as the pointer could point to la-la land if someone in // a different thread has closed the certificate store. // Attempt to set the option if (!::InternetSetOption( cpMainDlg->hRequest, INTERNET_OPTION_CLIENT_CERT_CONTEXT , (LPVOID) cpMainDlg->m_pHSCertContext, sizeof(CERT_CONTEXT) )) { // Display the error. CString sErrMsg; cpMainDlg->GetErrorMessage(GetLastError(), &sErrMsg); sMsg += _T(" "); sMsg += sErrMsg; } } } // end if (ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED == dwError) ... }
未来计划
我将在将来的修订版中添加对代理服务器的身份验证处理。目前,只能提供一组凭据,但该程序需要允许两组凭据,一组用于代理服务器,一组用于 Web 服务器。我还想添加一个文件附件对话框,用于测试 POST 方法以及二进制数据或伪表单数据附件。
历史
- 版本 1.0 - 首次发布。