WinHttp异步请求封装

CWinHttpClient.h

cpp 复制代码
#pragma once

#include <Windows.h>
#include <WinHttp.h>
#include <stdint.h>
#include <string>
#include <vector>
#include <functional>
#include <map>
#include <set>
#include <thread>
#include <time.h>
#include <tchar.h>

#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif

#pragma comment(lib, "winhttp.lib")
#define WINHTTP_AGENT_HTTPS "(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0)"

// 响应结果
class CWinHttpResult
{
public:
    CWinHttpResult() :code(0) {}
    std::string result;             //响应结果
    uint32_t code;                  //响应状态码
};

// 进度信息
typedef struct _WINHTTP_PROGRESS_INFO
{
    ULONGLONG ullCur;           // 当前下载量(字节)
    ULONGLONG ullTotal;         // 总数据量(字节)
    double lfProgress;          // 当前进度(0.0f - 1.0f)
    double lfSpeed;             // 当前速度(字节/秒)
    double lfRemainTime;        // 剩余时间(毫秒)
    clock_t costTime;           // 消耗时间(毫秒)
}WINHTTP_PROGRESS_INFO, *LPWINHTTP_PROGRESS_INFO;

// 异步数据
typedef struct _WHTTP_ASYNC_DATA
{
    DWORD_PTR dwContext;                // 上下文
    HANDLE hEvent;                      // 事件
    DWORD dwWait;                       // 事件等待结果
    DWORD dwSize;                       // 传输数据大小
    WINHTTP_ASYNC_RESULT AsyncResult;   // 异步结果
}WHTTP_ASYNC_DATA, *LPWHTTP_ASYNC_DATA;

// 请求头信息
using WinHttpRequestHeader = std::map<_tstring, std::set<_tstring>>;

// 进度回调
using WinHttpProgressCallback = std::function<bool(const WINHTTP_PROGRESS_INFO& progress)>;

// WinHttp客户端辅助类
class CWinHttpClient
{
public:

    CWinHttpClient();

    ~CWinHttpClient();

    // 关闭 
    void Abort();

    // 发送 GET 请求
    CWinHttpResult Get(const _tstring& strUrl, WinHttpProgressCallback cb = nullptr);

    // 发送 POST 请求
    CWinHttpResult Post(const _tstring& strUrl, const std::string& strParam, WinHttpProgressCallback cb = nullptr);

    // 添加请求头信息
    void AddRequestHeader(const _tstring strCaption, const _tstring strData);

    // 移除请求头信息
    void RemoveRequestHeader(const _tstring strCaption, const _tstring strData);

    // 清除请求头信息
    void ClearRequestHeader();

    // 设置用户代理字符串
    void SetAgent(const _tstring strAgent = _T(WINHTTP_AGENT_HTTPS));

    // 设置异步状态打印
    void SetPrintStatus(bool fEnable = true);

private:

    // 获取请求头字符串
    _tstring _GetRequestHeaderString();

    // 执行请求
    CWinHttpResult _DoRequest(const _tstring& strUrl, const _tstring& strMethod, const std::string& strParam);

    // 发送请求
    bool _SendRequest(HINTERNET hRequest, _tstring strHeader,LPVOID lpData, DWORD dwSize, DWORD_PTR dwContext);

    // 查询资源大小
    bool _QueryContentLength(HINTERNET hRequest, PULONGLONG lpUllContentLength);

    // 读取网络流
    bool _InternetReadData(HINTERNET hRequest, std::string& strData);

    // 等待异步事件
    bool _WaitForAsyncEvent(DWORD dwMilliseconds = INFINITE);

    // 获取状态码
    DWORD _GetStatusCode(HINTERNET hRequest);

    // 控制台打印
    static void ConsoleOutput(LPCTSTR pFormat, ...);

    // 错误输出
    void _PrintError(LPCTSTR lpszError) const;

    // 状态码打印
    static void _PrintStatus(DWORD dwCode, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength);

    // 状态回调
    static VOID CALLBACK WinHttpStatusCallback(
        IN HINTERNET hInternet,
        IN DWORD_PTR dwContext,
        IN DWORD dwInternetStatus,
        IN LPVOID lpvStatusInformation OPTIONAL,
        IN DWORD dwStatusInformationLength
    );

public:
    // 宽字节字符串转多字节字符串
    static std::string WStrToMultiStr(UINT CodePage, const std::wstring& str);

    // 多字节字符串转宽字节字符串
    static std::wstring MultiStrToWStr(UINT CodePage, const std::string& str);

    // 字符串转UTF-8编码字符串
    static std::string TStrToU8Str(const _tstring& str);

    // 字符串转宽字节字符串
    static std::wstring TStrToWStr(const _tstring& str);

    // 宽字节字符串转字符串
    static _tstring WStrToTStr(const std::wstring& str);

private:
    WinHttpRequestHeader    m_RequestHeader;        // 请求头
    WinHttpProgressCallback m_cbProgress;           // 进度回调
    WHTTP_ASYNC_DATA        m_AsyncData;            // 异步信息
    _tstring                m_strAgent;             // 代理字符串
    bool                    m_fAbort;               // 终止
    bool                    m_fPrint;               // 打印进度
};

CWinHttpClient.cpp

cpp 复制代码
#include "CWinHttpClient.h"

typedef struct _WINHTTP_URL_INFO
{
    std::wstring strScheme;
    std::wstring strHostName;
    std::wstring strUserName;
    std::wstring strPassword;
    std::wstring strUrlPath;
    std::wstring strExtraInfo;

    URL_COMPONENTS uc = { 0 };

    _WINHTTP_URL_INFO()
    {
        memset(&uc, 0, sizeof(uc));

        try
        {
            strScheme.resize(32);
            strHostName.resize(128);
            strUserName.resize(128);
            strPassword.resize(128);
            strUrlPath.resize(2048);
            strExtraInfo.resize(512);

            this->uc.dwStructSize = sizeof(this->uc);
            this->uc.lpszScheme = &this->strScheme[0];
            this->uc.dwSchemeLength = (DWORD)strScheme.size();
            this->uc.lpszHostName = &this->strHostName[0];
            this->uc.dwHostNameLength = (DWORD)strHostName.size();
            this->uc.lpszUserName = &this->strUserName[0];
            this->uc.dwUserNameLength = (DWORD)strUserName.size();
            this->uc.lpszPassword = &this->strPassword[0];
            this->uc.dwPasswordLength = (DWORD)strPassword.size();
            this->uc.lpszUrlPath = &this->strUrlPath[0];
            this->uc.dwUrlPathLength = (DWORD)strUrlPath.size();
            this->uc.lpszExtraInfo = &this->strExtraInfo[0];
            this->uc.dwExtraInfoLength = (DWORD)strExtraInfo.size();
        }
        catch (...)
        {

        }
    }
}WINHTTP_URL_INFO, *PWINHTTP_URL_INFO;

std::string CWinHttpClient::WStrToMultiStr(UINT CodePage, const std::wstring& str)
{
    int cbMultiByte = ::WideCharToMultiByte(CodePage, 0, str.c_str(), -1, NULL, 0, NULL, 0);
    std::string strResult(cbMultiByte, 0);
    size_t nConverted = ::WideCharToMultiByte(CodePage, 0, str.c_str(), (int)str.size(), &strResult[0], (int)strResult.size(), NULL, NULL);
    strResult.resize(nConverted);
    return strResult;
}

std::wstring CWinHttpClient::MultiStrToWStr(UINT CodePage, const std::string& str)
{
    int cchWideChar = ::MultiByteToWideChar(CodePage, 0, str.c_str(), -1, NULL, NULL);
    std::wstring strResult(cchWideChar, 0);
    size_t nConverted = ::MultiByteToWideChar(CodePage, 0, str.c_str(), (int)str.size(), &strResult[0], (int)strResult.size());
    strResult.resize(nConverted);
    return strResult;
}

std::string CWinHttpClient::TStrToU8Str(const _tstring& str)
{
#ifdef _UNICODE
    return WStrToMultiStr(CP_UTF8, str);
#else
    return WStrToMultiStr(CP_UTF8, MultiStrToWStr(CP_ACP, str));
#endif
}

std::wstring CWinHttpClient::TStrToWStr(const _tstring& str)
{
#ifdef _UNICODE
    return str;
#else
    return MultiStrToWStr(CP_ACP, str);
#endif
}

_tstring CWinHttpClient::WStrToTStr(const std::wstring& str)
{
#ifdef _UNICODE
    return str;
#else
    return WStrToMultiStr(CP_ACP, str);
#endif
}

static bool _CrackUrl(const _tstring& strUrl, PWINHTTP_URL_INFO lpUrlInfo)
{
    // 将 URL 分解到其组件部件中
    // https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpcrackurl
    // 即使在异步模式下使用 WinHTTP (即在 WinHttpOpen) 中设置了WINHTTP_FLAG_ASYNC时,此函数也会同步运行。 返回值指示成功或失败。
    if (!::WinHttpCrackUrl(CWinHttpClient::TStrToWStr(strUrl).c_str(), 0, 0, &lpUrlInfo->uc))
    {
        return false;
    }

    lpUrlInfo->strScheme.resize(wcslen(lpUrlInfo->strScheme.c_str()));
    lpUrlInfo->strExtraInfo.resize(wcslen(lpUrlInfo->strExtraInfo.c_str()));
    lpUrlInfo->strHostName.resize(wcslen(lpUrlInfo->strHostName.c_str()));
    lpUrlInfo->strUserName.resize(wcslen(lpUrlInfo->strUserName.c_str()));
    lpUrlInfo->strPassword.resize(wcslen(lpUrlInfo->strPassword.c_str()));
    lpUrlInfo->strUrlPath.resize(wcslen(lpUrlInfo->strUrlPath.c_str()));

    if (!lpUrlInfo->strExtraInfo.empty())
    {
        lpUrlInfo->strUrlPath += lpUrlInfo->strExtraInfo;
    }

    // 协议检查
    if (!(INTERNET_SCHEME_HTTPS == lpUrlInfo->uc.nScheme || INTERNET_SCHEME_HTTP == lpUrlInfo->uc.nScheme))
    {
        return false;
    }

    return true;
}

void CWinHttpClient::AddRequestHeader(const _tstring strCaption, const _tstring strData)
{
    auto itFind = m_RequestHeader.find(strCaption);
    if (m_RequestHeader.end() == itFind)
    {
        std::set<_tstring> setData;
        setData.insert(strData);
        m_RequestHeader.insert(std::make_pair(strCaption, setData));

    }
    else
    {
        itFind->second.insert(strData);
    }
}

void CWinHttpClient::RemoveRequestHeader(const _tstring strCaption, const _tstring strData)
{
    auto itFind = m_RequestHeader.find(strCaption);
    if (m_RequestHeader.end() == itFind)
    {
        return;
    }

    if (strData.empty())
    {
        m_RequestHeader.erase(itFind);
    }
    else
    {
        itFind->second.erase(strData);

        if (itFind->second.empty())
        {
            m_RequestHeader.erase(itFind);
        }
    }
}

void CWinHttpClient::ClearRequestHeader()
{
    m_RequestHeader.clear();
}

void CWinHttpClient::SetAgent(const _tstring strAgent)
{
    m_strAgent = strAgent;
}

void CWinHttpClient::SetPrintStatus(bool fEnable/* = true*/)
{
    m_fPrint = fEnable;
}

_tstring CWinHttpClient::_GetRequestHeaderString()
{
    _tstring strResult;

    size_t nItemIndex = 0;
    size_t nItemCount = m_RequestHeader.size();
    for (const auto& item : m_RequestHeader)
    {
        strResult += item.first;
        strResult += _T(": ");

        size_t nDataIndex = 0;
        size_t nDataCount = item.second.size();
        for (const auto& data : item.second)
        {
            strResult += data;
            nDataIndex++;
            if (nDataIndex < nDataCount)
            {
                strResult += _T(";");
            }
        }

        nItemIndex++;
        if (nItemIndex < nItemCount)
        {
            strResult += _T("\r\n");
        }
    }

    return strResult;
}

CWinHttpClient::CWinHttpClient()
    :
    m_fAbort(false),
    m_fPrint(false)
{
    m_AsyncData.hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
    m_AsyncData.dwContext = 0;
}

CWinHttpClient::~CWinHttpClient()
{
    if (NULL != m_AsyncData.hEvent)
    {
        ::CloseHandle(m_AsyncData.hEvent);
    }
}

void CWinHttpClient::Abort()
{
    m_fAbort = true;
    if (NULL != m_AsyncData.hEvent)
    {
        ::SetEvent(m_AsyncData.hEvent);
    }
}

CWinHttpResult CWinHttpClient::Get(const _tstring& strUrl, WinHttpProgressCallback cb/* = nullptr*/)
{
    m_cbProgress = cb;
    return _DoRequest(strUrl, _T("GET"), "");
}

CWinHttpResult CWinHttpClient::Post(const _tstring& strUrl, const std::string& strParam, WinHttpProgressCallback cb/* = nullptr*/)
{
    m_cbProgress = cb;
    return _DoRequest(strUrl, _T("POST"), strParam);
}

CWinHttpResult CWinHttpClient::_DoRequest(const _tstring& strUrl, const _tstring& strMethod, const std::string& strParam)
{
    WINHTTP_URL_INFO urlInfo;
    CWinHttpResult httpResult;
    HINTERNET hSession = NULL;
    HINTERNET hConnect = NULL;
    HINTERNET hRequest = NULL;

    if (!_CrackUrl(strUrl, &urlInfo))
    {
        return httpResult;
    }

    _tstring strHeader = _GetRequestHeaderString();
    std::wstring wstrAgent = TStrToWStr(m_strAgent);

    do
    {
        // 初始化 WinHTTP 函数的使用并返回 WinHTTP 会话句柄
        // https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpopen
        DWORD dwSessionFlags = WINHTTP_FLAG_ASYNC;    //WINHTTP_FLAG_ASYNC / WINHTTP_FLAG_SECURE_DEFAULTS
        hSession = ::WinHttpOpen(
            wstrAgent.c_str(),                      // 指向字符串变量的指针,此名称用作 HTTP 协议中的 用户代理
            WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,    // 所需的访问类型
            WINHTTP_NO_PROXY_NAME,                  // 代理访问时要使用的代理服务器的名称
            WINHTTP_NO_PROXY_BYPASS,                // 代理访问时要使用的代理服务器的密码
            dwSessionFlags                          // 指示影响此函数行为的各种选项的标志
        );

        if (NULL == hSession)
        {
            _PrintError(_T("WinHttpOpen"));
            break;
        }

        // 设置回调函数
        // https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsetstatuscallback
        DWORD dwNotificationFlags = WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS;
        WINHTTP_STATUS_CALLBACK statusCallback = ::WinHttpSetStatusCallback(
            hSession,               // 要为其设置回调的 HINTERNET 句柄
            WinHttpStatusCallback,  // 指向进度时要调用的回调函数的指针
            dwNotificationFlags,    // 无符号长整数值,该值指定标志以指示哪些事件激活回调函数
            0
        );
        if (WINHTTP_INVALID_STATUS_CALLBACK == statusCallback)
        {
            _PrintError(_T("WinHttpSetStatusCallback"));
            break;
        }

        // 设置状态回调上下文
        // https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsetoption
        DWORD_PTR dwContext = (DWORD_PTR)this;
        if (!::WinHttpSetOption(
            hSession,                       // 要设置数据的 HINTERNET 句柄
            WINHTTP_OPTION_CONTEXT_VALUE,   // 包含要设置的 Internet 选项的无符号长整数值
            &dwContext,                     // 指向包含选项设置的缓冲区的指针
            sizeof(dwContext)               // 包含 lpBuffer 缓冲区长度的无符号长整数值
        ))
        {
            _PrintError(_T("WinHttpSetOption"));
            break;
        }

        DWORD dwOptionFlag = (DWORD_PTR)WINHTTP_OPTION_REDIRECT_POLICY;
        DWORD dwOptionValue = (DWORD_PTR)WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS;
        /*if (!::WinHttpSetOption(
            hSession,
            dwOptionFlag,
            &dwOptionValue,
            sizeof(dwOptionValue)
        ))
        {
            _PrintError(_T("WinHttpSetOption"));
            break;
        }*/

        // 设置重定向次数
        dwOptionFlag = (DWORD_PTR)WINHTTP_OPTION_MAX_HTTP_AUTOMATIC_REDIRECTS;
        dwOptionValue = (DWORD_PTR)10;
        /*if (!::WinHttpSetOption(
            hSession,
            dwOptionFlag,
            &dwOptionValue,
            sizeof(dwOptionValue)
        ))
        {
            _PrintError(_T("WinHttpSetOption"));
            break;
        }*/

        // 指定 HTTP 请求的初始目标服务器,并将 HINTERNET 连接句柄返回到该初始目标的 HTTP 会话
        // https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpconnect
        // 即使 WinHTTP 在异步模式下使用(即在 WinHttpOpen中设置 WINHTTP_FLAG_ASYNC),此函数也会同步运行
        hConnect = ::WinHttpConnect(
            hSession,                       // 由先前调用 WinHttpOpen 返回的有效 HINTERNETWinHTTP 会话句柄
            urlInfo.strHostName.c_str(),    // HTTP 服务器的主机名
            urlInfo.uc.nPort,               // 建立连接的服务器上的 TCP/IP 端口
            0                               // 保留参数, 必须为0
        );

        if (NULL == hConnect)
        {
            _PrintError(_T("WinHttpConnect"));
            break;
        }

        // 创建 HTTP 请求句柄
        // https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpopenrequest
        DWORD dwRequestFlags = (INTERNET_SCHEME_HTTPS == urlInfo.uc.nScheme) ? WINHTTP_FLAG_SECURE : 0;
        hRequest = ::WinHttpOpenRequest(
            hConnect,                       //WinHttpConnect 返回的 HTTP 会话的 HINTERNET 连接句柄
            TStrToWStr(strMethod).c_str(),  //请求的 HTTP 谓词
            urlInfo.strUrlPath.c_str(),     //指定 HTTP 谓词的目标资源名称
            NULL,                           //HTTP 版本的字符串的指针
            WINHTTP_NO_REFERER,             //指定从中获取 请求 pwszObjectName 中的 URL 的文档的 URL
            WINHTTP_DEFAULT_ACCEPT_TYPES,   //指定客户端接受的媒体类型
            dwRequestFlags                  //Internet 标志值
        );

        // 等待异步请求完成
        if (!_WaitForAsyncEvent())
        {
            break;
        }

        hRequest = (HINTERNET)m_AsyncData.dwContext;
        if (NULL == hRequest)
        {
            _PrintError(_T("WinHttpOpenRequest"));
            break;
        }

        // 设置与 HTTP 事务相关的超时
        // https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsettimeouts
        if (!::WinHttpSetTimeouts(
            hRequest,   // WinHttpOpen 或 WinHttpOpenRequest 返回的 HINTERNET 句柄
            1000,       // 名称解析的超时值(以毫秒为单位)
            1000,       // 服务器连接请求的超时值(以毫秒为单位)
            3000,       // 发送请求的超时值(以毫秒为单位)
            5000        // 接收对请求的响应超超时值(以毫秒为单位)
        ))
        {
            _PrintError(_T("WinHttpSetTimeouts"));
            break;
        }

        // 发送请求
        if (!_SendRequest(hRequest, strHeader, (LPVOID)strParam.data(), (DWORD)strParam.size(), (DWORD_PTR)this))
        {
            _PrintError(_T("_SendRequest"));
            break;
        }

        // 等待异步请求完成
        if (!_WaitForAsyncEvent())
        {
            _PrintError(_T("_WaitForAsyncEvent"));
            break;
        }

        // 等待接收 WinHttpSendRequest 发起的 HTTP 请求的响应
        // https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpreceiveresponse
        if (!::WinHttpReceiveResponse(hRequest, NULL))
        {
            _PrintError(_T("WinHttpReceiveResponse"));
            break;
        }

        // 等待异步请求完成
        if (!_WaitForAsyncEvent())
        {
            _PrintError(_T("_WaitForAsyncEvent"));
            break;
        }

        // 获取响应码
        DWORD statusCodes = _GetStatusCode(hRequest);
        if (statusCodes < 200 || statusCodes >= 300)
        {
            break;
        }

        // 接收请求数据
        if (!_InternetReadData(hRequest, httpResult.result))
        {
            _PrintError(_T("_InternetReadData"));
            break;
        }

    } while (false);

    // 释放资源
    if (hRequest)::WinHttpCloseHandle(hRequest);
    if (hConnect)::WinHttpCloseHandle(hConnect);
    if (hSession)::WinHttpCloseHandle(hSession);

    return httpResult;
}

bool CWinHttpClient::_WaitForAsyncEvent(DWORD dwMilliseconds/* = INFINITE*/)
{
    // 等待异步事件响应
    m_AsyncData.dwWait = ::WaitForSingleObject(m_AsyncData.hEvent, dwMilliseconds);
    if (m_fAbort)
    {
        return false;
    }

    if (WAIT_OBJECT_0 == m_AsyncData.dwWait && 0 == m_AsyncData.AsyncResult.dwResult)
    {
        return true;
    }

    return false;
}

DWORD CWinHttpClient::_GetStatusCode(HINTERNET hRequest)
{
    DWORD dwRespCode = 0;
    DWORD dwBufferLength = sizeof(dwRespCode);

    // 查询请求状态码
    DWORD dwInfoLevel = WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER;
    if (!::WinHttpQueryHeaders(
        hRequest,                       //WinHttpOpenRequest 返回的 HINTERNET 请求句柄
        dwInfoLevel,                    //指定"查询信息标志"页上列出的属性标志和修饰符标志的组合
        WINHTTP_HEADER_NAME_BY_INDEX,   //标头名称
        &dwRespCode,                    //接收信息的缓冲区
        &dwBufferLength,                //数据缓冲区的长度
        NULL                            //从零开始的标头索引的指针,用于枚举具有相同名称的多个标头
    ))
    {
        return dwRespCode;
    }

    return dwRespCode;
}

bool CWinHttpClient::_InternetReadData(HINTERNET hRequest, std::string& strData)
{
    bool fResult = false;

    // 查询文件大小
    ULONGLONG ullContentLength = 0;
    if (!_QueryContentLength(hRequest, &ullContentLength))
    {
        _PrintError(_T("QueryContentLength"));
        return false;
    }

    // 分配数据缓冲
    try
    {
        strData.resize(ullContentLength, 0);
    }
    catch (...)
    {
        _PrintError(_T("std::string resize"));
        return false;
    }

    // 接收数据
    LPBYTE lpBufPos = (LPBYTE)strData.data();
    std::string strBuf;
    ULONGLONG ullDownloaded = 0;
    ULONGLONG ullTotalLength = ullContentLength;

    // 进度统计用变量
    clock_t refreshInterval = 1000;
    clock_t speedTime = 1000;
    clock_t curTime = ::clock();
    clock_t startTime = curTime;
    clock_t lastTime = curTime;
    clock_t lastProgressTime = curTime;
    double lfRemainTime = 0.0f;
    double lfSpeed = 0.0f;
    ULONGLONG ullLastDownload = 0;

    do
    {
        // 检查可用数据
        DWORD dwNumberOfBytesAvailable = 0;
        DWORD dwNumberOfBytesRead = 0;
        if (!::WinHttpQueryDataAvailable(hRequest, &dwNumberOfBytesAvailable))
        {
            _PrintError(_T("WinHttpQueryDataAvailable"));
            break;
        }

        if (!_WaitForAsyncEvent())
        {
            break;
        }
        dwNumberOfBytesAvailable = m_AsyncData.dwSize;

        if (dwNumberOfBytesAvailable > 0)
        {
            try
            {
                strBuf.resize(dwNumberOfBytesAvailable);
            }
            catch (...)
            {
                _PrintError(_T("std::vector resize"));
                break;
            }

            if (!::WinHttpReadData(hRequest, &strBuf[0], dwNumberOfBytesAvailable, NULL))
            {
                _PrintError(_T("WinHttpReadData"));
                break;
            }

            if (!_WaitForAsyncEvent())
            {
                break;
            }

            dwNumberOfBytesRead = m_AsyncData.dwSize;

            // 写入到缓冲
            memcpy(lpBufPos, &strBuf[0], dwNumberOfBytesRead);
            lpBufPos += dwNumberOfBytesRead;
            ullDownloaded += dwNumberOfBytesRead;
        }

        // 进度计算
        if (m_cbProgress)
        {
            // 速度计算
            clock_t curTime = ::clock();
            if (curTime - lastTime >= speedTime || curTime - startTime < speedTime || 0 == dwNumberOfBytesAvailable)
            {
                if (curTime - lastTime >= speedTime)
                {
                    lfSpeed = (double)(ullDownloaded - ullLastDownload) / ((double)speedTime / 1000.0f);
                }

                if (curTime - startTime < speedTime)
                {
                    lfSpeed = (double)(ullDownloaded - ullLastDownload) / ((double)(curTime - lastTime) / 1000.0f);
                }

                if (isinf(lfSpeed) || isnan(lfSpeed))
                {
                    lfSpeed = 0.0f;
                }

                // 计算剩余时间
                lfRemainTime = ((double)(ullTotalLength - ullDownloaded)) / lfSpeed;

                lastTime = curTime;
                ullLastDownload = ullDownloaded;
            }

            // 进度报告
            if (curTime - lastProgressTime > refreshInterval || 0 == dwNumberOfBytesAvailable)
            {
                WINHTTP_PROGRESS_INFO progress = { 0 };
                progress.lfProgress = (double)ullDownloaded / (double)ullTotalLength;
                progress.ullCur = ullDownloaded;
                progress.ullTotal = ullTotalLength;
                progress.lfSpeed = lfSpeed;
                progress.costTime = curTime - startTime;
                progress.lfRemainTime = lfRemainTime;

                if (!m_cbProgress(progress))
                {
                    break;
                }

                lastProgressTime = curTime;
            }
        }

        // 检查读取是否结束
        if (!dwNumberOfBytesAvailable)
        {
            fResult = true;
            break;
        }

    } while (true);

    return fResult;
}

bool CWinHttpClient::_SendRequest(
    HINTERNET hRequest, 
    _tstring strHeader,
    LPVOID lpData, 
    DWORD dwSize,
    DWORD_PTR dwContext
)
{
    std::wstring wstrHeader = TStrToWStr(strHeader);
    LPCWSTR lpHeader = (LPCWSTR)wstrHeader.data();
    DWORD dwHeaderSize = (DWORD)wstrHeader.size();

    if (wstrHeader.empty())
    {
        lpHeader = WINHTTP_NO_ADDITIONAL_HEADERS;
        dwHeaderSize = 0;
    }

    // 将指定的请求发送到 HTTP 服务器
    if (::WinHttpSendRequest(
        hRequest,               //WinHttpOpenRequest 返回的 HINTERNET 句柄
        lpHeader,               //要追加到请求的其他标头
        dwHeaderSize,           //附加标头的长度(以字符为单位)
        lpData,                 //请求标头之后发送的任何可选数据
        dwSize,                 //可选数据的长度(以字节为单位)
        dwSize,                 //发送的总数据的长度
        dwContext               //上下文
    ))
    {
        return true;
    }

    // 安全 HTTP 服务器需要客户端证书
    if (ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED != ::GetLastError())
    {
        return false;
    }

    // 设置 Internet 选项
    // https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsetoption
    if (!::WinHttpSetOption(
        hRequest,
        WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
        WINHTTP_NO_CLIENT_CERT_CONTEXT,
        0
    ))
    {
        return false;
    }

    // 再次发送请求
    if (!::WinHttpSendRequest(
        hRequest,               //WinHttpOpenRequest 返回的 HINTERNET 句柄
        lpHeader,               //要追加到请求的其他标头
        dwHeaderSize,           //附加标头的长度(以字符为单位)
        lpData,                 //请求标头之后发送的任何可选数据
        dwSize,                 //可选数据的长度(以字节为单位)
        dwSize,                 //发送的总数据的长度
        NULL
    ))
    {
        return false;
    }

    return true;
}

bool CWinHttpClient::_QueryContentLength(HINTERNET hRequest, PULONGLONG lpUllContentLength)
{
    ULONGLONG ullContentLength = 0;
    DWORD dwBufferLength = sizeof(ullContentLength);

    // 检索与 HTTP 请求关联的标头信息
    // https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpqueryheaders

    DWORD dwInfoLevel = WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER64;
    if (!::WinHttpQueryHeaders(
        hRequest, 
        dwInfoLevel,                    // 属性标志和修饰符标志的组合
        WINHTTP_HEADER_NAME_BY_INDEX,   // 标头名称
        &ullContentLength,              // 接收信息的缓冲区
        &dwBufferLength,                // 数据缓冲区的长度
        NULL                            // 从零开始的标头索引的指针,用于枚举具有相同名称的多个标头
    ))
    {
        _PrintError(_T("WinHttpQueryHeaders"));
        *lpUllContentLength = 0;
        return false;
    }

    *lpUllContentLength = ullContentLength;

    return true;
}

void CWinHttpClient::_PrintError(LPCTSTR lpszError) const
{
    ConsoleOutput(_T("[Error][LastError: %d Error: %d Result: %d][%s]\r\n"),
        ::GetLastError(), 
        m_AsyncData.AsyncResult.dwError,
        m_AsyncData.AsyncResult.dwResult,
        lpszError
    );
}

void CWinHttpClient::ConsoleOutput(LPCTSTR pFormat, ...)
{
    size_t nCchCount = MAX_PATH;
    _tstring strResult(nCchCount, 0);
    va_list args;

    va_start(args, pFormat);

    do
    {
        //格式化输出字符串
        int nSize = _vsntprintf_s(&strResult[0], nCchCount, _TRUNCATE, pFormat, args);
        if (-1 != nSize)
        {
            HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
            ::WriteConsole(console, strResult.c_str(), nSize, NULL, NULL);
            break;
        }

        //缓冲大小超限终止
        if (nCchCount >= INT32_MAX)
        {
            break;
        }

        //重新分配缓冲
        nCchCount *= 2;
        strResult.resize(nCchCount);

    } while (true);

    va_end(args);
}

#define WINHTTP_STATUS_TEXT(_code)  std::make_pair(_code, _T(#_code)),

void CWinHttpClient::_PrintStatus(DWORD dwCode, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)
{
    std::map<DWORD, _tstring> mapWinHttpCode = {
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_RESOLVING_NAME             )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_NAME_RESOLVED              )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER       )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER        )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SENDING_REQUEST            )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_REQUEST_SENT               )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE         )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED          )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION         )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED          )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_HANDLE_CREATED             )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING             )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_DETECTING_PROXY            )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_REDIRECT                   )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE      )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SECURE_FAILURE             )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE          )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE             )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_READ_COMPLETE              )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE             )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_REQUEST_ERROR              )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE       )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE    )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE             )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE          )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_GETPROXYSETTINGS_COMPLETE  )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SETTINGS_WRITE_COMPLETE    )
        WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SETTINGS_READ_COMPLETE     )
    };

    auto itFind = mapWinHttpCode.find(dwCode);

    if (mapWinHttpCode.end() != itFind)
    {
        ConsoleOutput(_T("%08X %-50s "), itFind->first, itFind->second.c_str());
    }

    if (dwCode == WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER ||
        dwCode == WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER ||
        dwCode == WINHTTP_CALLBACK_STATUS_NAME_RESOLVED ||
        dwCode == WINHTTP_CALLBACK_STATUS_REDIRECT
        )
    {
        if (NULL != lpvStatusInformation)
        {
            LPWSTR lpData = (LPWSTR)lpvStatusInformation;
            ConsoleOutput(_T("Data %s"), WStrToTStr(lpData).c_str());
        }
    }

    if (dwCode == WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE ||
        dwCode == WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE ||
        dwCode == WINHTTP_CALLBACK_STATUS_REQUEST_SENT ||
        dwCode == WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED ||
        dwCode == WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE ||
        dwCode == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE
        )
    {
        if (NULL != lpvStatusInformation)
        {
            LPDWORD lpData = (LPDWORD)lpvStatusInformation;
            ConsoleOutput(_T("Data %d(%08X)"), *lpData, *lpData);
        }
    }

    if (dwCode == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR
        )
    {
        if (NULL != lpvStatusInformation)
        {
            LPWINHTTP_ASYNC_RESULT lpData = (LPWINHTTP_ASYNC_RESULT)lpvStatusInformation;
            ConsoleOutput(_T("Error %d(%08X) Result: %d(%08X)"), 
                lpData->dwError, lpData->dwError,
                lpData->dwResult, lpData->dwResult
            );
        }
    }

    ConsoleOutput(_T("\r\n"));
}

VOID CALLBACK CWinHttpClient::WinHttpStatusCallback(
    IN HINTERNET hInternet,
    IN DWORD_PTR dwContext,
    IN DWORD dwInternetStatus,
    IN LPVOID lpvStatusInformation OPTIONAL,
    IN DWORD dwStatusInformationLength
)
{
    WHTTP_ASYNC_DATA* pAsyncData = NULL;
    CWinHttpClient* pThis = (CWinHttpClient*)dwContext;
    if (nullptr == pThis)
    {
        return;
    }

    if (pThis->m_fPrint)
    {
        _PrintStatus(dwInternetStatus, lpvStatusInformation, dwStatusInformationLength);
    }

    pAsyncData = &pThis->m_AsyncData;

    switch (dwInternetStatus)
    {

        // 关闭与服务器的连接。 lpvStatusInformation 参数为 NULL。
    case WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION:
    {
        break;
    }

    // 已成功连接到服务器。 lpvStatusInformation 参数包含指向 LPWSTR 的指针,该指针以点表示法指示服务器的 IP 地址。
    case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
    {
        LPWSTR lpData = (LPWSTR)lpvStatusInformation;
        break;
    }

    // 连接到服务器。 lpvStatusInformation 参数包含指向 LPWSTR 的指针,该指针以点表示法指示服务器的 IP 地址。
    case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER:
    {
        LPWSTR lpData = (LPWSTR)lpvStatusInformation;
        break;
    }

    // 已成功关闭与服务器的连接。 lpvStatusInformation 参数为 NULL。
    case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED:
    {
        break;
    }

    // 可以使用 WinHttpReadData 检索数据。 lpvStatusInformation 参数指向包含可用数据字节数的 DWORD。 
    // dwStatusInformationLength 参数本身是 4 (DWORD) 的大小。
    case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:// WinHttpQueryDataAvailable 完成
    {
        DWORD lpSize = *(LPDWORD)lpvStatusInformation;
        pAsyncData->dwSize = lpSize;
        pAsyncData->AsyncResult.dwResult = 0;
        ::SetEvent(pAsyncData->hEvent);
        break;
    }

    // 已创建 HINTERNET 句柄。 lpvStatusInformation 参数包含指向 HINTERNET 句柄的指针。
    case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED:
    {
        LPHINTERNET pHandle = (LPHINTERNET)lpvStatusInformation;
        pAsyncData->dwContext = (DWORD_PTR)*pHandle;
        ::SetEvent(pAsyncData->hEvent);
        break;
    }

    // 此句柄值已终止。 lpvStatusInformation 参数包含指向 HINTERNET 句柄的指针。 此句柄不再有回调。
    case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
    {
        LPHINTERNET pHandle = (LPHINTERNET)lpvStatusInformation;
        break;
    }

    // 响应标头已收到,可用于 WinHttpQueryHeaders。 lpvStatusInformation 参数为 NULL。
    case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: // WinHttpReceiveResponse 完成
    {
        pAsyncData->AsyncResult.dwResult = 0;
        ::SetEvent(pAsyncData->hEvent);
        break;
    }

    // 从服务器收到中间 (100 级别) 状态代码消息。 lpvStatusInformation 参数包含指向指示状态代码的 DWORD 的指针。
    case WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE:
    {
        LPDWORD pStatusCode = (LPDWORD)lpvStatusInformation;
        break;
    }

    // 已成功找到服务器的 IP 地址。 lpvStatusInformation 参数包含指向 LPWSTR 的指针,该指针指示已解析的名称。
    case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED:
    {
        LPWSTR lpResolved = (LPWSTR)lpvStatusInformation;
        break;
    }

    // 已成功从服务器读取数据。 
    // lpvStatusInformation 参数包含指向调用 WinHttpReadData 中指定的缓冲区的指针。 
    // dwStatusInformationLength 参数包含读取的字节数。
    // WinHttpWebSocketReceive 使用时,lpvStatusInformation 参数包含指向WINHTTP_WEB_SOCKET_STATUS结构的指针,
    // dwStatusInformationLength 参数指示 lpvStatusInformation 的大小。
    case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: // WinHttpReadData 完成
    {
        LPBYTE* ppBuf = (LPBYTE*)lpvStatusInformation;
        DWORD dwRead = dwStatusInformationLength;
        pAsyncData->AsyncResult.dwResult = 0;
        pAsyncData->dwSize = dwRead;
        ::SetEvent(pAsyncData->hEvent);
        break;
    }

    // 等待服务器响应请求。 lpvStatusInformation 参数为 NULL。
    case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE:
    {
        break;
    }

    // HTTP 请求将自动重定向请求。 lpvStatusInformation 参数包含指向指示新 URL 的 LPWSTR 的指针。 
    // 此时,应用程序可以使用重定向响应读取服务器返回的任何数据,并且可以查询响应标头。 它还可以通过关闭句柄来取消操作。
    case WINHTTP_CALLBACK_STATUS_REDIRECT:
    {
        LPWSTR lpData = (LPWSTR)lpvStatusInformation;
        break;
    }

    // 发送 HTTP 请求时出错。 lpvStatusInformation 参数包含指向WINHTTP_ASYNC_RESULT结构的指针。 
    // 其 dwResult 成员指示被调用函数的 ID,dwError 指示返回值。
    case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
    {
        DWORD dwError = ERROR_WINHTTP_INCORRECT_HANDLE_STATE;
        LPWINHTTP_ASYNC_RESULT pAsyncResult = (LPWINHTTP_ASYNC_RESULT)lpvStatusInformation;
        pAsyncData->AsyncResult = *pAsyncResult;
        ::SetEvent(pAsyncData->hEvent);
        break;
    }

    // 已成功将信息请求发送到服务器。 lpvStatusInformation 参数包含指向 DWORD 的指针,该指针指示发送的字节数。
    case WINHTTP_CALLBACK_STATUS_REQUEST_SENT:
    {
        LPDWORD pSentBytes = (LPDWORD)lpvStatusInformation;
        break;
    }

    // 查找服务器名称的 IP 地址。 lpvStatusInformation 参数包含指向要解析的服务器名称的指针。
    case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME:
    {
        LPWSTR lpName = (LPWSTR)lpvStatusInformation;
        break;
    }

    // 已成功从服务器收到响应。 lpvStatusInformation 参数包含指向指示接收字节数的 DWORD 的指针。
    case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED:
    {
        LPDWORD pRecv = (LPDWORD)lpvStatusInformation;
        break;
    }

    // 在与服务器建立安全 (HTTPS) 连接时遇到一个或多个错误。 
    // lpvStatusInformation 参数包含指向 DWORD 的指针,该指针是错误值的按位 OR 组合。 
    // 有关详细信息,请参阅 lpvStatusInformation 的说明。
    case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
    {
        DWORD dwCode = *(LPDWORD)lpvStatusInformation;
        switch (dwCode)
        {
            // 证书吊销检查已启用,但吊销检查未能验证证书是否已吊销。 用于检查吊销的服务器可能无法访问
        case WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED:
        {
            break;
        }
        // SSL 证书无效
        case WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT:
        {
            break;
        }
        // SSL 证书已吊销
        case WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED:
        {
            break;
        }
        // 函数不熟悉生成服务器证书的证书颁发机构
        case WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA:
        {
            break;
        }
        // SSL 证书公用名 (主机名字段) 不正确,例如,如果输入 www.microsoft.com 且证书上的公用名显示 www.msn.com
        case WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID:
        {
            break;
        }
        // 从服务器收到的 SSL 证书日期不正确。 证书已过期。
        case WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID:
        {
            break;
        }
        // 应用程序在加载 SSL 库时遇到内部错误
        case WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR:
        {
            break;
        }
        }

        break;
    }

    // 将信息请求发送到服务器。 lpvStatusInformation 参数为 NULL。
    case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
    {
        break;
    }

    // 请求已成功完成。 
    // lpvStatusInformation 参数是传递给 WinHttpSendRequest (初始请求正文) 的 lpOptional 值,
    // dwStatusInformationLength 参数指示 (传递到 WinHttpSendRequest) 传递到 winHttpSendRequest 的 dwOptionalLength 值成功写入此类初始正文字节的数目。
    case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:  // WinHttpSendRequest 完成
    {
        pAsyncData->AsyncResult.dwResult = 0;
        ::SetEvent(pAsyncData->hEvent);
        break;
    }

    // 数据已成功写入服务器。 lpvStatusInformation 参数包含指向 DWORD 的指针,该指针指示写入的字节数。
    // 当 WinHttpWebSocketSend 使用时, lpvStatusInformation 参数包含指向WINHTTP_WEB_SOCKET_STATUS结构的指针,
    // dwStatusInformationLength 参数指示 lpvStatusInformation 的大小。
    case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:    // WinHttpWriteData 完成
    {
        pAsyncData->AsyncResult.dwResult = 0;
        ::SetEvent(pAsyncData->hEvent);
        break;
    }

    // 通过调用 WinHttpGetProxyForUrlEx 启动的操作已完成。 可以使用 WinHttpReadData 检索数据。
    case WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE:
    {
        break;
    }
    // 通过调用 WinHttpWebSocketClose 成功关闭了连接。 lpvStatusInformation 参数为 NULL。
    case WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE:
    {
        break;
    }
    // 通过调用 WinHttpWebSocketShutdown 成功关闭连接。 lpvStatusInformation 参数为 NULL。
    case WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE:
    {
        break;
    }
    }
}

main.cpp

cpp 复制代码
#include <iostream>
#include "CWinHttpClient.h"

#define UPDATE_URL1          _T("https://gitee.com/flame_cyclone/fc_font_tool/raw/master/Release/update.json")
#define UPDATE_URL2          _T("https://gitee.com/flame_cyclone/fc_font_tool/releases/download/1.0.0.4/FC_Font_Tool.exe")
#define TEST_NVIDIA_DRIVER   _T("https://cn.download.nvidia.com/Windows/561.09/561.09-desktop-win10-win11-64bit-international-dch-whql.exe")
#define TEST_AMD_DRIVER      _T("https://drivers.amd.com/drivers/whql-amd-software-adrenalin-edition-24.8.1-win10-win11-aug-rdna.exe")

int main()
{
    CWinHttpClient obj;

    obj.AddRequestHeader(_T("Content-Type"), _T("application/json;charset=UTF-8"));
    obj.AddRequestHeader(_T("Accept"), _T("*/*"));
    obj.AddRequestHeader(_T("Referer"), _T("https://www.amd.com/"));
    obj.SetAgent();

    CWinHttpResult res = obj.Get(TEST_AMD_DRIVER, [](const WINHTTP_PROGRESS_INFO& progress) {
        _tprintf(_T("Time: %.3lfs Progress: %.3lf%% %.1lfMB/%.1lfMB Speed: %.1lf Mbps %.1lfMB/s %.1lfKB/s RemainTime: %.1lfs\n"),
        (double)progress.costTime / 1000.0f,
        progress.lfProgress * 100, 
        (double)progress.ullCur / (1024.0f * 1024.0f), (double)progress.ullTotal / (1024.0f * 1024.0f),
        progress.lfSpeed / (1024.0f * 1024.0f) * 8.0f,
        progress.lfSpeed / (1024.0f * 1024.0f),
        progress.lfSpeed / (1024.0f), progress.lfRemainTime
        );

    return true;
        });

    return 0;
}
相关推荐
唐诺4 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨5 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客5 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin5 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos6 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室7 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0017 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我587 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc7 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
呆萌很8 小时前
C++ 集合 list 使用
c++