c++Get http獲取JSON 以及WinInet:HTTPS 請求出現無效的證書頒發機構的處理

首先,微軟提供的WinInet庫封裝了對網頁訪問的方法。

 最近工作需要從https服務器獲取數據,都知道https和http網頁的訪問方式不同,多了一道證書認證程序,這樣就使得https在請求起來比http要複雜的多;好在,WinInet庫中提供了對https網頁請求的處理,這樣就不需要在使用openssl中的一些方法來複雜化程序了。

 下面貼上我的解決前的代碼,再對比我遇到問題之後的代碼,在通過實際遇到的問題和環境來闡述:

  解決前代碼:`#include

include

include

include

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

include

include

include

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.04.01 中,但是,該請求必須重新提交即使 ERROR_SUCCESS 將返回。

Microsoft 已經確認這是 InternetErrorDlg API 中的問題。注 2: SECURITY_FLAG_IGNORE_UNKNOWN_CA 在 Internet Explorer 3.03.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;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章