c++Get http获取JSON 以及WinInet:HTTPS 请求出现无效的证书颁发机构的处理

首先,微软提供的WinInet库封装了对网页访问的方法。

 最近工作需要从https服务器获取数据,都知道https和http网页的访问方式不同,多了一道证书认证程序,这样就使得https在请求起来比http要复杂的多;好在,WinInet库中提供了对https网页请求的处理,这样就不需要在使用openssl中的一些方法来复杂化程序了。

 下面贴上我的解决前的代码,再对比我遇到问题之后的代码,在通过实际遇到的问题和环境来阐述:

解决前代码:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include "Windows.h"
#include "wininet.h"
using namespace std;
//链接需要 wininet.lib
#pragma comment(lib,"wininet.lib")
int main(int argc, char* argv[])
{
    LPCTSTR lpszAgent = "WinInetGet/0.1";
    //初始化
    HINTERNET hInternet = InternetOpen(lpszAgent,
        INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    LPCTSTR lpszServerName = "data.btcchina.com";//"ssl.google-analytics.com"; //设置server
    INTERNET_PORT nServerPort = INTERNET_DEFAULT_HTTPS_PORT; // HTTPS端口443
    LPCTSTR lpszUserName = NULL; //无登录用户名
    LPCTSTR lpszPassword = NULL; //无登录密码
    DWORD dwConnectFlags = 0;
    DWORD dwConnectContext = 0;
    //连接
    HINTERNET hConnect = InternetConnect(hInternet,
        lpszServerName, nServerPort,
        lpszUserName, lpszPassword,
        INTERNET_SERVICE_HTTP,
        dwConnectFlags, dwConnectContext);
    //使用Get
    LPCTSTR lpszVerb = "GET";
    LPCTSTR lpszObjectName = "/data/ticker";
    LPCTSTR lpszVersion = NULL;    // 默认.
    LPCTSTR lpszReferrer = NULL;   // 没有引用页
    LPCTSTR *lplpszAcceptTypes = NULL; // Accpet所有类型.
    DWORD dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
        INTERNET_FLAG_KEEP_CONNECTION |
        INTERNET_FLAG_NO_AUTH |
        INTERNET_FLAG_NO_COOKIES |
        INTERNET_FLAG_NO_UI |
        //设置启用HTTPS
        INTERNET_FLAG_SECURE |
        INTERNET_FLAG_RELOAD;
    DWORD dwOpenRequestContext = 0;
    //初始化Request
    HINTERNET hRequest = HttpOpenRequest(hConnect, lpszVerb, lpszObjectName, lpszVersion,
        lpszReferrer, lplpszAcceptTypes,
        dwOpenRequestFlags, dwOpenRequestContext);
    //发送Request
HttpSendRequest(hRequest, NULL, 0, NULL, 0); 
//获得HTTP Response Header信息
    DWORD dwInfoLevel = HTTP_QUERY_RAW_HEADERS_CRLF;
    DWORD dwInfoBufferLength = 2048;
    BYTE *pInfoBuffer = (BYTE *)malloc(dwInfoBufferLength + 2);
    while(!HttpQueryInfo(hRequest, dwInfoLevel, pInfoBuffer, &dwInfoBufferLength, NULL)) {
        DWORD dwError = GetLastError();
        if(dwError == ERROR_INSUFFICIENT_BUFFER) {
            free(pInfoBuffer);
            pInfoBuffer = (BYTE *)malloc(dwInfoBufferLength + 2);
        } else {
            fprintf(stderr, "HttpQueryInfo failed, error = %d (0x%x)/n",
                GetLastError(), GetLastError());
            break;
        }
    }
    pInfoBuffer[dwInfoBufferLength] = '/0';
    pInfoBuffer[dwInfoBufferLength + 1] = '/0';
    printf("%S", pInfoBuffer); //很奇怪HttpQueryInfo保存的格式是wchar_t 和下面的InternetReadFile不一样
    free(pInfoBuffer);
    //HTTP Response 的 Body, 需要的内容就在里面
    DWORD dwBytesAvailable;
    while(InternetQueryDataAvailable(hRequest, &dwBytesAvailable, 0, 0)) {
        BYTE *pMessageBody = (BYTE *)malloc(dwBytesAvailable + 1);
        DWORD dwBytesRead;
        BOOL bResult = InternetReadFile(hRequest, pMessageBody,
            dwBytesAvailable, &dwBytesRead);
        if(!bResult) {
            fprintf(stderr, "InternetReadFile failed, error = %d (0x%x)/n",
                GetLastError(), GetLastError());
            break;
        }
        if(dwBytesRead == 0)
            break; // End of File.
        pMessageBody[dwBytesRead] = '/0';
        printf("%s", pMessageBody); //InternetReadFile读出来的是普通的char. InternetReadFileEx 似乎是有宽字节版本的
        
        ofstream out("ofs.txt");
        std::string s = (char *)pMessageBody;
        out << s.c_str()<< endl;

        free(pMessageBody);
    }
    getchar();
}

解决后代码:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include "Windows.h"
#include "wininet.h"

using namespace std;
//链接需要 wininet.lib
#pragma comment(lib,"wininet.lib")

int main(int argc, char* argv[])
{
    LPCTSTR lpszAgent = "WinInetGet/0.1";
    //初始化
    HINTERNET hInternet = InternetOpen(lpszAgent,
        INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    LPCTSTR lpszServerName = "data.btcchina.com";//"ssl.google-analytics.com"; //设置server
    INTERNET_PORT nServerPort = INTERNET_DEFAULT_HTTPS_PORT; // HTTPS端口443
    LPCTSTR lpszUserName = NULL; //无登录用户名
    LPCTSTR lpszPassword = NULL; //无登录密码
    DWORD dwConnectFlags = 0;
    DWORD dwConnectContext = 0;
    //连接
    HINTERNET hConnect = InternetConnect(hInternet,
        lpszServerName, nServerPort,
        lpszUserName, lpszPassword,
        INTERNET_SERVICE_HTTP,
        dwConnectFlags, dwConnectContext);
    //使用Get
    LPCTSTR lpszVerb = "GET";
    LPCTSTR lpszObjectName = "/data/ticker";
    LPCTSTR lpszVersion = NULL;    // 默认.
    LPCTSTR lpszReferrer = NULL;   // 没有引用页
    LPCTSTR *lplpszAcceptTypes = NULL; // Accpet所有类型.
    DWORD dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
        INTERNET_FLAG_KEEP_CONNECTION |
        INTERNET_FLAG_NO_AUTH |
        INTERNET_FLAG_NO_COOKIES |
        INTERNET_FLAG_NO_UI |
        //设置启用HTTPS
        INTERNET_FLAG_SECURE |
        INTERNET_FLAG_RELOAD;
    DWORD dwOpenRequestContext = 0;
    //初始化Request
    HINTERNET hRequest = HttpOpenRequest(hConnect, lpszVerb, lpszObjectName, lpszVersion,
        lpszReferrer, lplpszAcceptTypes,
        dwOpenRequestFlags, dwOpenRequestContext);
    //发送Request
again:
    DWORD dwError = 0;
    if (!HttpSendRequest(hRequest, NULL, 0, NULL, 0))
    {
        dwError = GetLastError();
    }
    if (dwError == ERROR_INTERNET_INVALID_CA)
    {
        fprintf(stderr, "HttpSendRequest failed, error = %d (0x%x)/n",
            dwError, dwError );

        DWORD dwFlags;
        DWORD dwBuffLen = sizeof(dwFlags);
        InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS,
            (LPVOID)&dwFlags, &dwBuffLen);

        dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
        InternetSetOption (hRequest, INTERNET_OPTION_SECURITY_FLAGS,
                                &dwFlags, sizeof(dwFlags));
        goto again;
    }

    //获得HTTP Response Header信息
    DWORD dwInfoLevel = HTTP_QUERY_RAW_HEADERS_CRLF;
    DWORD dwInfoBufferLength = 2048;
    BYTE *pInfoBuffer = (BYTE *)malloc(dwInfoBufferLength + 2);
    while(!HttpQueryInfo(hRequest, dwInfoLevel, pInfoBuffer, &dwInfoBufferLength, NULL)) {
        DWORD dwError = GetLastError();
        if(dwError == ERROR_INSUFFICIENT_BUFFER) {
            free(pInfoBuffer);
            pInfoBuffer = (BYTE *)malloc(dwInfoBufferLength + 2);
        } else {
            fprintf(stderr, "HttpQueryInfo failed, error = %d (0x%x)/n",
                GetLastError(), GetLastError());
            break;
        }
    }
    pInfoBuffer[dwInfoBufferLength] = '/0';
    pInfoBuffer[dwInfoBufferLength + 1] = '/0';
    printf("%S", pInfoBuffer); //很奇怪HttpQueryInfo保存的格式是wchar_t 和下面的InternetReadFile不一样
    free(pInfoBuffer);
    //HTTP Response 的 Body, 需要的内容就在里面
    DWORD dwBytesAvailable;
    while(InternetQueryDataAvailable(hRequest, &dwBytesAvailable, 0, 0)) {
        BYTE *pMessageBody = (BYTE *)malloc(dwBytesAvailable + 1);
        DWORD dwBytesRead;
        BOOL bResult = InternetReadFile(hRequest, pMessageBody,
            dwBytesAvailable, &dwBytesRead);
        if(!bResult) {
            fprintf(stderr, "InternetReadFile failed, error = %d (0x%x)/n",
                GetLastError(), GetLastError());
            break;
        }
        if(dwBytesRead == 0)
            break; // End of File.
        pMessageBody[dwBytesRead] = '/0';
        printf("%s", pMessageBody); //InternetReadFile读出来的是普通的char. InternetReadFileEx 似乎是有宽字节版本的

        ofstream out("ofs.txt");
        std::string s = (char *)pMessageBody;
        out << s.c_str()<< endl;

        free(pMessageBody);
    }
    getchar();
}

大家看到HttpOpenRequest这个函数中,dwOpenRequestFlag参数:

DWORD dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
        INTERNET_FLAG_KEEP_CONNECTION |
        INTERNET_FLAG_NO_AUTH |
        INTERNET_FLAG_NO_COOKIES |
        INTERNET_FLAG_NO_UI |
        //设置启用HTTPS
        INTERNET_FLAG_SECURE |
        INTERNET_FLAG_RELOAD;

要request到https网页的数据,INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP和INTERNET_FLAG_SECURE两个选项要设置。

从上面的前后两段代码,大家应该能看到逻辑的变化在哪里,就在于HttpSendRequest这个函数的返回值的处理上。

看HttpSendRequest这层逻辑的处理,你会好奇为什么要用到这样的逻辑呢?答案其实并不是那么好告诉你的,因为,这样设置逻辑是因为微软当时在设计这个库的时候留下的一个漏洞。

因为,https协议涉及到证书认证问题,而IE低版本内核的浏览器打开你要请求的https Url的时候,会出现证书认证失败,(比如我这里的:btc.china.com/data/ticker),而高级版本的浏览器可能就不会有任何问题。

在解决问题前,我的环境是Win7系统,IE10浏览器,在我运行程序的时候一切正常,能正常获取到程序,浏览器也能打开网页看到网页上的数据,但是当我把程序发布release然后交给运维测试的时候,他那边环境是(win server 2003, IE7环境),这就出现了问题,他那边获取不到那个请求https网站的数据,于是我建议他们按照步骤通过浏览器端安装该网站的认证证书,安装之后浏览器可以看到数据,但是运行程序并不能正常获得数据,这就是我的问题所在。

于是,就问Google大婶们,无果,所以只有解铃还须系铃人了,遂到微软的问题解决网站寻求帮助,结果,查出来这是微软设计的一个缺陷,但是他们给出了很好的解决办法,那就是忽略证书认证。[微软解决办法:http://support.microsoft.com/kb/182888/zh-cn]
  考虑到有的时候,有些人会打不开微软的这个网站,我在这里把他复制粘贴出来,如下:

客户端不知道有关颁发服务器证书的证书颁发机构时,就会发生此错误。通过安装证书颁发机构的根证书,问题可能得到解决。可以从 Internet Explorer 查看所有已安装的证书列表。从视图菜单上,单击 Internet 选项,单击内容选项卡,单击机构。

很可能绕过此 WinInet 应用程序中的错误,而不安装证书。有两种方法来处理该错误。您可以使用类似于以下示例的代码。

方法 1。与用户界面 (生成类似于 Internet Explorer 的消息框):
   ...
   Again:
   if (!HttpSendRequest (hReq,...))
       dwError = GetLastError ();

   if (dwError == ERROR_INTERNET_INVALID_CA)
   {
       // Make sure to check return code from InternetErrorDlg
       // user may click either OK or Cancel. In case of Cancel
       // request should not be resumbitted.
       InternetErrorDlg (GetDesktopWindow(),
                         hReq,
                         ERROR_INTERNET_INVALID_CA,
                         FLAGS_ERROR_UI_FILTER_FOR_ERRORS |
                         FLAGS_ERROR_UI_FLAGS_GENERATE_DATA |
                         FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS,
                         NULL);
      goto again;
   }
   ...
                
方法 2。而无需用户界面:
   ...
   Again:
   if (!HttpSendRequest (hReq,...))
      dwError = GetLastError ();
   if (dwError == ERROR_INTERNET_INVALID_CA)
   {
      DWORD dwFlags;
      DWORD dwBuffLen = sizeof(dwFlags);

      InternetQueryOption (hReq, INTERNET_OPTION_SECURITY_FLAGS,
            (LPVOID)&dwFlags, &dwBuffLen);

      dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
      InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS,
                            &dwFlags, sizeof (dwFlags) );
      goto again;
   }
   ...
                
与 MFC WinInet 类可以使用相似的逻辑。在这种情况下,下列 MFC 方法对应于 WinInet 上面使用的 Api:

CInternetFile::SendRequest
CInternetFile::QueryOption
CInternetFile::SetOption
CInternetFile::ErrorDlg
请注意缺少 Visual C++ 5.0 CInternetFile::ErrorDlg,CInternetFile::QueryOption 和 CInternetFile::SetOption 上的文档。请参阅 Inet.cpp MFC 源代码文件的信息如何使用此方法。

注 1: InternetErrorDlg 可能会返回下列值:
   ERROR_SUCCESS
   ERROR_CANCELLED
   ERROR_INTERNET_FORCE_RETRY.
                
仅当返回 ERROR_INTERNET_FORCE_RETRY 时,才应重新提交请求。在 Internet Explorer 4.0 和 4.01 中,但是,该请求必须重新提交即使 ERROR_SUCCESS 将返回。

Microsoft 已经确认这是 InternetErrorDlg API 中的问题。注 2: SECURITY_FLAG_IGNORE_UNKNOWN_CA 在 Internet Explorer 3.0 和 3.02 未实现。

InternetErrorDlg 仍然起作用,但有以下例外。此 api 生成对话框中不允许忽略无效的证书颁发机构的错误 ;它是只是通知页该用户不能查看。

注 3: 在错误发生之前,不能设置选项,将忽略此错误。您首先必须尝试发送请求、 收到错误消息,然后设置选项 (或调用 InternetErrorDlg),然后重新提交。

我用的是提供的第二个方法无用户界面的解决方法。然后这样大家应该就会明白我那里的处理逻辑为啥会那个样子了。

备注下:在此之前我遇到了C++服务器从微信API接口获取https的JSON返回数据查阅相关资料这篇文章主要是写的http获取JSON的返回值[https://blog.csdn.net/lt623265189/article/details/78413226],第一次找到的也是这篇文章但是后边发现自己应该用https踩了一个巨坑.两天时间说起来很短,但是项目时间越来越短我内心是煎熬的.终于皇天不负,我找到了这里.
解决方案:(具体源代码看链接)
一 端口原来的http是INTERNET_DEFAULT_HTTP_PORT 80改成 INTERNET_DEFAULT_HTTPS_PORT 443
二 原来的INTERNET_FLAG_RELOAD 改为用这个dwOpenRequestFlags 代替设置启用HTTPS
m_hRequest = HttpOpenRequestA(m_hConnect, strRequestType.c_str(), strPageName.c_str(), “HTTPS/1.1”, NULL, NULL, dwOpenRequestFlags, NULL);

DWORD dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
INTERNET_FLAG_KEEP_CONNECTION |
INTERNET_FLAG_NO_AUTH |
INTERNET_FLAG_NO_COOKIES |
INTERNET_FLAG_NO_UI |
//设置启用HTTPS
INTERNET_FLAG_SECURE |
INTERNET_FLAG_RELOAD;

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值