自学内容网 自学内容网

WinHttp辅助类封装, GET, POST, 多线程下载文件

CWinHttpHelper

#pragma once

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

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

typedef struct _WINHTTP_URL_INFO WINHTTP_URL_INFO, *LPWINHTTP_URL_INFO;
typedef struct _WINHTTP_PACKAGE_INFO WINHTTP_PACKAGE_INFO, *LPWINHTTP_PACKAGE_INFO;

#define WINHTTP_HTTPS_DOWNLOAD_AGENT LR"(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)"

#define WINHTTP_MAX_CONNS_PER_SERVER                    (256)           // 服务器最大连接数
#define WINHTTP_DOWNLOAD_BLOCK_SIZE                     (1024 * 32)     // 下载缓冲大小
#define WINHTTP_DOWNLOAD_MINIMUM_SIZE                   (1024 * 4)      // 单线程最小下载大小
#define WINHTTP_PROGRESS_UPDATE_INTERVAL_TIME           (1000)          // 下载进度更新时间
#define WINHTTP_SPEED_UPDATE_INTERVAL_TIME              (1000)          // 下载速度更新时间
#define WINHTTP_FILE_BACKUP_MAX_CONUT                   (0)             // 最多备份数量
#define WINHTTP_MAX_THREAD_COUNT                        (64)            // 最大下载线程数

#define WINHTTP_RESOLVE_TIME_OUT                        (1000)          // 解析超时
#define WINHTTP_CONNECT_TIME_OUT                        (1000)          // 连接超时
#define WINHTTP_SEND_TIME_OUT                           (2000)          // 发送超时
#define WINHTTP_RECEIVE_TIME_OUT                        (5000)         // 接收超时

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

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

// WinHttp助手类
class CWinHttpHelper
{
public:
    CWinHttpHelper();
    ~CWinHttpHelper();

    //
    // @brief: 添加请求头信息
    // @param: strCaption       头名
    // @param: strData          头数据
    // @ret: void
    void AddRequestHeader(
        const _tstring strCaption, 
        const _tstring strData
    );

    //
    // @brief: 移除请求头信息
    // @param: strCaption       头名
    // @param: strData          头数据
    // @ret: void
    void RemoveRequestHeader(
        const _tstring strCaption, 
        const _tstring strData
    );

    //
    // @brief: 清除请求头信息
    // @ret: void
    void ClearRequestHeader();

    //
    // @brief: 获取下载内容大小
    // @param: strUrl           URL链接
    // @param: pUllLength       内容长度缓冲指针
    // @ret: bool               操作成功与否
    bool GetContentLength(
        const _tstring& strUrl, 
        PULONGLONG pUllLength
    );

    //
    // @brief: 下载保存到文件
    // @param: strUrl           URL链接
    // @param: strFile          保存文件路径
    // @param: cbProgress       下载进度回调函数
    // @param: dwThreadCount    下载线程数
    // @ret: bool               操作成功与否
    bool DownloadToFile(
        const _tstring& strUrl, 
        _tstring strFile, 
        std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress = nullptr,
        DWORD dwThreadCount = 8
    );

    //
    // @brief: 下载保存到缓存
    // @param: strUrl           URL链接
    // @param: lpBuf            保存缓冲
    // @param: dwBufSize        缓冲大小
    // @param: cbProgress       下载进度回调函数
    // @param: dwThreadCount    下载线程数
    // @ret: bool               操作成功与否
    bool DownloadToBuffer(
        const _tstring& strUrl, 
        std::string& vData,
        std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress = nullptr,
        DWORD dwThreadCount = 8
    );

    //
    // @brief: 发送Get请求
    // @param: strUrl           URL链接
    // @param: cbProgress       进度回调
    // @param: dwThreadCount    线程数
    // @ret: CWinHttpResult     响应结果
    CWinHttpResult Get(
        const _tstring& strUrl,
        std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress = nullptr,
        DWORD dwThreadCount = 1
    );

    //
    // @brief: 发送Post请求
    // @param: strUrl           URL链接
    // @param: strParam         请求参数
    // @param: cbProgress       进度回调
    // @param: dwThreadCount    线程数
    // @ret: CWinHttpResult     响应结果
    CWinHttpResult Post(
        const _tstring& strUrl,
        std::string& strParam,
        std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress = nullptr,
        DWORD dwThreadCount = 1
    );

private:

    //
    // @brief: 执行请求
    // @param: strUrl           URL链接
    // @param: strMethod        请求方法
    // @param: strParam         请求参数
    // @param: cbProgress       进度回调
    // @param: dwThreadCount    线程数
    // @ret: CWinHttpResult   响应结果
    CWinHttpResult _DoSendRequest(
        const _tstring& strUrl,
        const _tstring& strMethod,
        const std::string& strParam,
        std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress = nullptr,
        DWORD dwThreadCount = 1
    );

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

    // 清空文件内容
    void _TruncateFile(const _tstring& strPath);

    // 下载前备份文件
    bool _BackupFile(const _tstring& strPath);

    // 进度处理
    void _ProcessProcess(std::vector<WINHTTP_PACKAGE_INFO>& taskPackages);

    // 分段下载
    bool _PartialDownload(
        const _tstring& strUrl, 
        const _tstring& strUrlFileName, 
        const _tstring& strVerb, 
        LPVOID lpBuf,
        ULONGLONG ullContentLength,
        DWORD dwThreadCount
    );

    // 任务包处理
    bool _PartialPackageTask(
        WINHTTP_PACKAGE_INFO& packInfo
    );

    // 分解链接
    bool _CrackUrl(
        const _tstring& strUrl, 
        LPWINHTTP_URL_INFO lpUci
    );

    // 查询是否支持接收范围
    bool _IsSupportAcceptRanges(HINTERNET hRequest);

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

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

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

    // 读取网络流
    bool _InternetReadData(
        HINTERNET hRequest, 
        LPWINHTTP_PACKAGE_INFO lpPackage,
        bool fFile = false,
        std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress = nullptr
    );

    // 设置请求数据范围
    bool _SetRequestDataRange(
        HINTERNET hRequest, 
        LONGLONG nBegin, 
        LONGLONG nEng, 
        bool isHasEnd = TRUE
    );

    // 下载分包
    bool _DownloadPackage(LPWINHTTP_PACKAGE_INFO pPackageInfo);
    
    void _SleepMillisecond(int millisecond) const;

    // 打印警告
    void _PrintWarn(LPCTSTR lpszError) const;

    // 打印错误
    void _PrintError(LPCTSTR lpszError) const;

    // 宽字节字符串转多字节字符串
    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 void _InternetStatusCallback(
        HINTERNET hInternet,
        DWORD_PTR dwContext,
        DWORD dwInternetStatus,
        LPVOID lpvStatusInformation,
        DWORD dwStatusInformationLength
    );

private:

    std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> m_cbProgress;
    std::map<_tstring, std::set<_tstring>> m_RequestHeader;
    bool m_bSupportAcceptRanges;
    bool m_bHasSize;
    bool m_bAbort;
};

CWinHttpHelper.cpp

#include "CWinHttpHelper.h"
#include <strsafe.h>
#include <time.h>
#include <thread>
#include <algorithm>
#include <schannel.h>
#include <deque>

#pragma comment(lib, "winhttp.lib")

#define HTTP_READ_BLOCK_SIZE                     (1024 * 8)  // 读取块大小

#define INTERNET_MAX_HOST_NAME_LENGTH   256
#define INTERNET_MAX_USER_NAME_LENGTH   128
#define INTERNET_MAX_PASSWORD_LENGTH    128
#define INTERNET_MAX_PORT_NUMBER_LENGTH 5           // INTERNET_PORT is unsigned short
#define INTERNET_MAX_PORT_NUMBER_VALUE  65535       // maximum unsigned short value
#define INTERNET_MAX_PATH_LENGTH        2048
#define INTERNET_MAX_SCHEME_LENGTH      32          // longest protocol name length
#define INTERNET_MAX_URL_LENGTH         (INTERNET_MAX_SCHEME_LENGTH \
                                        + sizeof("://") \
                                        + INTERNET_MAX_PATH_LENGTH)

// 设置安全标志
const DWORD g_dwSecurityFlags =   SECURITY_FLAG_IGNORE_UNKNOWN_CA |
                                SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE |
                                SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
                                SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;

// 下载分包信息
typedef struct _WINHTTP_PACKAGE_INFO
{
    enum DownloadStatus {
        DS_Downloading = 0,
        DS_DownloadFinish = 1,
        DS_DownloadFailed = 2,
        DS_DownloadCancel = 3,
    };

    _tstring strUrl;                    // 链接
    _tstring strFileName;               // 保存文件名
    _tstring strVerb;                   // 谓词
    LPVOID lpBuffer;                    // 保存缓冲
    ULONGLONG ullDataStartPos;          // 请求数据起始位置
    ULONGLONG ullDataEndPos;            // 请求数据结束位置
    ULONGLONG ullDataCurPos;            // 当前数据起始位置
    ULONGLONG ullDataCurSize;           // 已接收数据大小
    ULONGLONG ullDataTotalSize;         // 请求数据总大小
    DWORD dwStatus;                     // 下载状态
    DWORD dwPartID;                     // 任务ID

    _WINHTTP_PACKAGE_INFO(
        const _tstring& url = _T(""), 
        const _tstring& name = _T(""), 
        const _tstring& verb = _T("GET"),
        LPVOID lpBuffer = nullptr, 
        ULONGLONG begin = 0, 
        ULONGLONG size = 0, 
        DWORD id = 0
    )
    {
        this->lpBuffer = lpBuffer;
        this->strFileName = name;
        this->strUrl = url;
        this->strVerb = verb;
        this->ullDataCurPos = begin;
        this->ullDataTotalSize = size;
        this->dwPartID = id;
        this->dwStatus = DownloadStatus::DS_Downloading;
        this->ullDataStartPos = begin;
        this->ullDataEndPos = size > 0 ? begin + size - 1 : begin;
        this->ullDataCurSize = 0;
    }

}WINHTTP_PACKAGE_INFO, *LPWINHTTP_PACKAGE_INFO;

typedef struct _WINHTTP_URL_INFO
{
    WCHAR szScheme[INTERNET_MAX_SCHEME_LENGTH];
    WCHAR szHostName[INTERNET_MAX_HOST_NAME_LENGTH];
    WCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH];
    WCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH];
    WCHAR szUrlPath[INTERNET_MAX_URL_LENGTH];
    WCHAR szExtraInfo[MAX_PATH];
    URL_COMPONENTS uc = { 0 };

    _WINHTTP_URL_INFO()
    {
        memset(this, 0, sizeof(*this));
        this->uc.dwStructSize = sizeof(this->uc);
        this->uc.lpszUrlPath = this->szUrlPath;
        this->uc.dwUrlPathLength = _countof(this->szUrlPath);
        this->uc.lpszScheme = this->szScheme;
        this->uc.dwSchemeLength = _countof(this->szScheme);
        this->uc.lpszHostName = this->szHostName;
        this->uc.dwHostNameLength = _countof(this->szHostName);
        this->uc.lpszUserName = this->szUserName;
        this->uc.dwUserNameLength = _countof(this->szUserName);
        this->uc.lpszPassword = this->szPassword;
        this->uc.dwPasswordLength = _countof(this->szPassword);
        this->uc.lpszExtraInfo = this->szExtraInfo;
        this->uc.dwExtraInfoLength = _countof(this->szExtraInfo);
    }
}WINHTTP_URL_INFO, *LPWINHTTP_URL_INFO;

CWinHttpHelper::CWinHttpHelper()
    :
    m_bAbort(false),
    m_bSupportAcceptRanges(false),
    m_bHasSize(false)
{
}

CWinHttpHelper::~CWinHttpHelper()
{

}

std::string CWinHttpHelper::_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 CWinHttpHelper::_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 CWinHttpHelper::_TStrToU8Str(
    const _tstring& str
)
{
#ifdef _UNICODE
    return _WStrToMultiStr(CP_UTF8, str);
#else
    return _WStrToMultiStr(CP_UTF8, _MultiStrToWStr(CP_ACP, str));
#endif
}

std::wstring CWinHttpHelper::_TStrToWStr(
    const _tstring& str
)
{
#ifdef _UNICODE
    return str;
#else
    return _MultiStrToWStr(CP_ACP, str);
#endif
}

void CWinHttpHelper::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 CWinHttpHelper::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 CWinHttpHelper::ClearRequestHeader()
{
    m_RequestHeader.clear();
}

_tstring CWinHttpHelper::_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;
}

bool CWinHttpHelper::GetContentLength(
    const _tstring& strUrl, 
    PULONGLONG lpUllContentLength
)
{
    WINHTTP_URL_INFO urlInfo;
    HINTERNET hSession = NULL;
    HINTERNET hConnect = NULL;
    HINTERNET hRequest = NULL;
    bool bSuccess = false;

    if (strUrl.empty() || NULL == lpUllContentLength)
    {
        return false;
    }

    // 分解URL
    if (!_CrackUrl(strUrl, &urlInfo))
    {
        return false;
    }

    _tstring strHeader = _GetRequestHeaderString();
    _tstring strParam;

    do
    {
        // 初始化应用程序对 WinHttp 函数的使用
        hSession = ::WinHttpOpen(
            WINHTTP_HTTPS_DOWNLOAD_AGENT, //指向字符串变量的指针,该变量包含调用 WinHTTP 函数的应用程序或实体的名称
            WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, //所需的访问类型
            WINHTTP_NO_PROXY_NAME, //代理访问时要使用的代理服务器的名称
            WINHTTP_NO_PROXY_BYPASS, //代理访问时要使用的代理服务器的密码
            WINHTTP_FLAG_SECURE_DEFAULTS // 指示影响此函数行为的各种选项的标志
        );

        // 打开给定站点的 HTTP 会话
        hConnect = ::WinHttpConnect(
            hSession,//由先前调用 WinHttpOpen 返回的有效 HINTERNETWinHTTP 会话句柄
            urlInfo.szHostName, //HTTP 服务器的主机名
            urlInfo.uc.nPort, //建立连接的服务器上的 TCP/IP 端口
            0 //保留参数, 必须为0
        );

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

        // 创建 HTTPS / HTTP 请求句柄
        DWORD dwFlags = (INTERNET_SCHEME_HTTPS == urlInfo.uc.nScheme) ? WINHTTP_FLAG_SECURE : 0;
        hRequest = ::WinHttpOpenRequest(
            hConnect, //WinHttpConnect 返回的 HTTP 会话的 HINTERNET 连接句柄
            _TStrToWStr(_T("GET")).c_str(), //请求的 HTTP 谓词
            urlInfo.szUrlPath, //指定 HTTP 谓词的目标资源名称
            NULL, //HTTP 版本的字符串的指针
            WINHTTP_NO_REFERER,//指定从中获取 请求 pwszObjectName 中的 URL 的文档的 URL
            WINHTTP_DEFAULT_ACCEPT_TYPES, //指定客户端接受的媒体类型
            dwFlags   //Internet 标志值
        );

        if (NULL == hRequest)
        {
            _PrintError(_T("HttpOpenRequest"));
            break;
        }

        // 设置安全标志
        DWORD dwSecurityFlags = g_dwSecurityFlags;
        if (!::WinHttpSetOption(hRequest,  WINHTTP_OPTION_SECURITY_FLAGS, &dwSecurityFlags, sizeof(dwSecurityFlags)))
        {
            _PrintError(_T("WinHttpSetOption"));
            break;
        }

        // 设置客户端证书上下文
        if (!::WinHttpSetOption(hRequest, WINHTTP_OPTION_CLIENT_CERT_CONTEXT, WINHTTP_NO_CLIENT_CERT_CONTEXT, 0))
        {
            _PrintError(_T("WinHttpSetOption"));
            break;
        }

        // 设置超时
        if (!::WinHttpSetTimeouts(hRequest, 500, 500, 1000, 1000))
        {
            _PrintError(_T("WinHttpSetTimeouts"));
            break;
        }

        // 发送请求
        bool fResult = false;
        for (int i = 0; i < 5; i++)
        {
            if (_SendRequest(hRequest, strHeader, nullptr, 0))
            {
                fResult = true;
                break;
            }

            _PrintError(_T("_SendRequest Retry"));
            _tprintf(_T("GetContentLength _SendRequest Retry: %d\r\n"), i + 1);
        }

        if (!fResult)
        {
            _PrintError(_T("SendRequest"));
            break;
        }

        // 等待接收 WinHttpSendRequest 发起的 HTTP 请求的响应
        if (!::WinHttpReceiveResponse(hRequest, NULL))
        {
            _PrintError(_T("WinHttpReceiveResponse"));
            break;
        }

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

        // 记录是否支持接收范围请求
        m_bSupportAcceptRanges = _IsSupportAcceptRanges(hRequest);

        // 查询文件大小
        if (!_QueryContentLength(hRequest, lpUllContentLength))
        {
            _PrintError(_T("QueryContentLength"));
            break;
        }

        bSuccess = true;

    } while (false);

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

    return bSuccess;
}

bool CWinHttpHelper::DownloadToFile(
    const _tstring& strUrl, 
    _tstring strFile, 
    std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress,
    DWORD dwThreadCount
)
{
    ULONGLONG ullContentLength = 0;

    if (strUrl.empty())
    {
        return false;
    }

    if (strFile.empty())
    {
        size_t nPos = strUrl.find_last_of(_T("/"));
        if (_tstring::npos != nPos)
        {
            strFile = strUrl.substr(nPos + 1);
        }
    }

    if (strFile.empty())
    {
        return false;
    }

    m_cbProgress = cbProgress;

    //限制线程数量
    dwThreadCount = dwThreadCount < 1 ? 1 : dwThreadCount;
    dwThreadCount = dwThreadCount > WINHTTP_MAX_THREAD_COUNT ? WINHTTP_MAX_THREAD_COUNT : dwThreadCount;

    bool fResult = false;
    for (int i = 0; i < 10; i++)
    {
        if (GetContentLength(strUrl, &ullContentLength))
        {
            fResult = true;
            break;
        }

        _tprintf(_T("GetContentLength retry: %d\r\n"), i + 1);
    }

    // 获取资源大小
    if (!fResult)
    {
        _PrintError(_T("GetContentLength"));
        return false;
    }

    // 不支持范围请求则仅单线程下载
    if (!m_bSupportAcceptRanges || !m_bHasSize)
    {
        dwThreadCount = 1;
    }

    // 备份文件
    if (!_BackupFile(strFile))
    {
        _PrintError(_T("BackupFile"));
        return false;
    }

    // 删除文件, 防止下载后改名冲突
    if (!::DeleteFile(strFile.c_str()) && (ERROR_FILE_NOT_FOUND != ::GetLastError()))
    {
        return false;
    }

    // 清空一下临时文件(如果存在的话)
    _tstring strTmpFile = strFile + _T(".tmp");
    (void)_TruncateFile(strTmpFile);

    // 多线程分段下载
    if (!_PartialDownload(strUrl, strTmpFile, _T("GET"),  NULL, ullContentLength, dwThreadCount))
    {
        _PrintError(_T("_PartialDownload"));
        return false;
    }

    // 下载完成则修改文件名
    ::MoveFile(strTmpFile.c_str(), strFile.c_str());

    return true;
}

bool CWinHttpHelper::DownloadToBuffer(
    const _tstring& strUrl, 
    std::string& vData,
    std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress,
    DWORD dwThreadCount
)
{
    ULONGLONG ullContentLength = 0;

    if (strUrl.empty())
    {
        return false;
    }

    m_cbProgress = cbProgress;

    //限制线程数量
    dwThreadCount = dwThreadCount < 1 ? 1 : dwThreadCount;
    dwThreadCount = dwThreadCount > WINHTTP_MAX_THREAD_COUNT ? WINHTTP_MAX_THREAD_COUNT : dwThreadCount;

    // 获取资源大小
    if (!GetContentLength(strUrl, &ullContentLength))
    {
        _PrintError(_T("GetContentLength"));
        return false;
    }

    // 不支持范围请求则仅单线程下载
    if (!m_bSupportAcceptRanges || !m_bHasSize)
    {
        dwThreadCount = 1;
    }

    try
    {
        vData.resize(ullContentLength);

        // 多线程分段下载
        if (!_PartialDownload(strUrl, _T(""), _T("GET"), (LPVOID)vData.data(), ullContentLength, dwThreadCount))
        {
            _PrintError(_T("PartialDownload"));
            return false;
        }
    }
    catch (...)
    {
        return false;
    }

    return true;
}

CWinHttpResult CWinHttpHelper::Get(
    const _tstring& strUrl, 
    std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress/* = nullptr*/,
    DWORD dwThreadCount/* = 1*/
)
{
    return _DoSendRequest(strUrl, _T("GET"), "", cbProgress, dwThreadCount);
}

CWinHttpResult CWinHttpHelper::Post(
    const _tstring& strUrl, 
    std::string& strParam, 
    std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress/* = nullptr*/,
    DWORD dwThreadCount/* = 1*/
)
{
    return _DoSendRequest(strUrl, _T("POST"), strParam, cbProgress, dwThreadCount);
}

CWinHttpResult CWinHttpHelper::_DoSendRequest(
    const _tstring& strUrl,
    const _tstring& strMethod,
    const std::string& strParam,
    std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress/* = nullptr*/,
    DWORD dwThreadCount
)
{
    CWinHttpResult httpsResponse;

    WINHTTP_URL_INFO urlInfo;
    ULONGLONG ullContentLength = 0;
    HINTERNET hSession = NULL;
    HINTERNET hConnect = NULL;
    HINTERNET hRequest = NULL;
    bool bSuccess = false;

    // 分解URL
    if (!_CrackUrl(strUrl, &urlInfo))
    {
        return httpsResponse;
    }

    _tstring strHeader = _GetRequestHeaderString();

    do
    {
        // 初始化应用程序对 WinHttp 函数的使用
        hSession = ::WinHttpOpen(
            WINHTTP_HTTPS_DOWNLOAD_AGENT, //指向字符串变量的指针,该变量包含调用 WinHTTP 函数的应用程序或实体的名称
            WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, //所需的访问类型
            WINHTTP_NO_PROXY_NAME, //代理访问时要使用的代理服务器的名称
            WINHTTP_NO_PROXY_BYPASS, //代理访问时要使用的代理服务器的密码
            WINHTTP_FLAG_SECURE_DEFAULTS // 指示影响此函数行为的各种选项的标志
        );

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

        // 打开给定站点的 HTTP 会话
        hConnect = ::WinHttpConnect(
            hSession,//由先前调用 WinHttpOpen 返回的有效 HINTERNETWinHTTP 会话句柄
            urlInfo.szHostName, //HTTP 服务器的主机名
            urlInfo.uc.nPort, //建立连接的服务器上的 TCP/IP 端口
            0 //保留参数, 必须为0
        );

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

        // 创建 HTTPS / HTTP 请求句柄
        DWORD dwFlags = (INTERNET_SCHEME_HTTPS == urlInfo.uc.nScheme) ? WINHTTP_FLAG_SECURE : 0;
        hRequest = ::WinHttpOpenRequest(
            hConnect, //WinHttpConnect 返回的 HTTP 会话的 HINTERNET 连接句柄
            _TStrToWStr(strMethod).c_str(), //请求的 HTTP 谓词
            urlInfo.szUrlPath, //指定 HTTP 谓词的目标资源名称
            NULL, //HTTP 版本的字符串的指针
            WINHTTP_NO_REFERER,//指定从中获取 请求 pwszObjectName 中的 URL 的文档的 URL
            WINHTTP_DEFAULT_ACCEPT_TYPES, //指定客户端接受的媒体类型
            dwFlags   //Internet 标志值
        );


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

        // 等待接收 WinHttpSendRequest 发起的 HTTP 请求的响应
        if (!::WinHttpReceiveResponse(hRequest, NULL))
        {
            break;
        }

        // 记录状态码
        httpsResponse.code = _GetStatusCode(hRequest);

        // 查询文件大小
        if (!_QueryContentLength(hRequest, &ullContentLength))
        {
            _PrintError(_T("QueryContentLength"));
            break;
        }

        // 捕获异常
        try
        {
            httpsResponse.result.resize(ullContentLength);

            //资源过小检查
            if (ullContentLength < WINHTTP_DOWNLOAD_MINIMUM_SIZE)
            {
                dwThreadCount = 1;
            }

            // 不支持范围请求则仅单线程下载
            if (!m_bSupportAcceptRanges || !m_bHasSize || ullContentLength < WINHTTP_DOWNLOAD_MINIMUM_SIZE)
            {
                dwThreadCount = 1;
            }

            if (dwThreadCount <= 1)
            {
                WINHTTP_PACKAGE_INFO packageInfo;
                packageInfo.lpBuffer = (LPVOID)httpsResponse.result.data();
                packageInfo.ullDataTotalSize = ullContentLength;
                packageInfo.ullDataCurPos = 0;
                if (!_InternetReadData(hRequest, &packageInfo, false, cbProgress))
                {
                    _PrintError(_T("_InternetReadData"));
                    break;
                }
            }
            else
            {
                if (!_PartialDownload(strUrl, _T(""), strMethod, (LPVOID)httpsResponse.result.data(), ullContentLength, dwThreadCount))
                {
                    break;
                }
            }
        }
        catch (...)
        {

        }

        bSuccess = true;
    } while (false);

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

    return httpsResponse;
}

void CWinHttpHelper::_TruncateFile(const _tstring& strPath)
{
    HANDLE hFile = INVALID_HANDLE_VALUE;
    hFile = ::CreateFile(strPath.c_str(),
        GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE,
        NULL,
        TRUNCATE_EXISTING,
        FILE_ATTRIBUTE_ARCHIVE,
        NULL);

    if (INVALID_HANDLE_VALUE != hFile)
    {
        ::CloseHandle(hFile);
    }
}

bool CWinHttpHelper::_BackupFile(const _tstring& strPath)
{
    TCHAR szBuf[MAX_PATH] = { 0 };
    _tstring strBakFile;
    bool isBackupOk = false;

    if (0 == WINHTTP_FILE_BACKUP_MAX_CONUT)
    {
        return true;
    }

    for (int i = 1; i <= WINHTTP_FILE_BACKUP_MAX_CONUT; i++)
    {
        ::StringCchPrintf(szBuf, _countof(szBuf), _T("%02d"), i);
        strBakFile = strPath + _T(".") + szBuf + _T(".bak");
        if (::MoveFile(strPath.c_str(), strBakFile.c_str()))
        {
            isBackupOk = true;
            break;
        }

        if (ERROR_FILE_NOT_FOUND == ::GetLastError())
        {
            isBackupOk = true;
            break;
        }
    }

    //备份失败, 则删除最早一个备份文件
    if (!isBackupOk)
    {
        _tstring strOld;
        _tstring strNew;

        ::StringCchPrintf(szBuf, _countof(szBuf), _T("%02d"), 1);
        strOld = strPath + _T(".") + szBuf + _T(".bak");

        // 无法删除
        if (!::DeleteFile(strOld.c_str()))
        {
            return false;
        }

        for (int i = 1; i < WINHTTP_FILE_BACKUP_MAX_CONUT; i++)
        {
            ::StringCchPrintf(szBuf, _countof(szBuf), _T("%02d"), i);
            strNew = strPath + _T(".") + szBuf + _T(".bak");;
            ::StringCchPrintf(szBuf, _countof(szBuf), _T("%02d"), i + 1);
            strOld = strPath + _T(".") + szBuf + _T(".bak");;
            if (!::MoveFile(strOld.c_str(), strNew.c_str()))
            {
                break;
            }
        }

        ::StringCchPrintf(szBuf, _countof(szBuf), _T("%02d"), WINHTTP_FILE_BACKUP_MAX_CONUT);
        strBakFile = strPath + _T(".") + szBuf + _T(".bak");
        if (::MoveFile(strPath.c_str(), strBakFile.c_str()))
        {
            isBackupOk = true;
        }
    }

    return isBackupOk;
}

void CWinHttpHelper::_ProcessProcess(std::vector<WINHTTP_PACKAGE_INFO>& taskPackages)
{
    ULONGLONG ullLastDownload = 0;
    clock_t refreshInterval = WINHTTP_PROGRESS_UPDATE_INTERVAL_TIME;
    clock_t lastTime = ::clock();
    clock_t lastProgressTime = ::clock();
    clock_t speedTime = WINHTTP_SPEED_UPDATE_INTERVAL_TIME;
    double lfRemainTime = 0;
    double lfSpeed = 0.0f;
    bool fQuit = false;

    clock_t startTime = ::clock();

    std::deque<double> dqCurSpeed;

    // 等待所有线程结束下载
    while (!fQuit && !m_bAbort)
    {
        ULONGLONG ullDownloaded = 0;
        ULONGLONG ullTotalLength = 0;
        DWORD dwDownloadingCount = 0;

        // 统计已下载量 与 总下载量
        for (const auto& item : taskPackages)
        {
            ullDownloaded += item.ullDataCurSize;
            ullTotalLength += item.ullDataTotalSize;

            if (WINHTTP_PACKAGE_INFO::DownloadStatus::DS_Downloading == item.dwStatus)
            {
                dwDownloadingCount++;
            }
        }

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

            if (0 == dwDownloadingCount)
            {
                lfSpeed = (double)(ullDownloaded - ullLastDownload) / ((double)(curTime - lastTime) / 1000.0f);
            }

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

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

            // 统计最近 10 次下载速度平均值作为下载预估剩余时间的速度
            if (dqCurSpeed.size() < 10)
            {
                dqCurSpeed.push_back(lfSpeed);
            }
            else
            {
                dqCurSpeed.pop_front();
                dqCurSpeed.push_back(lfSpeed);
            }

            double lfAveSpeed = 0.0f;
            for (const auto& item : dqCurSpeed)
            {
                lfAveSpeed += item;
            }

            lfAveSpeed /= dqCurSpeed.size();

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

            lastTime = curTime;
            ullLastDownload = ullDownloaded;
        }

        // 进度报告
        if (curTime - lastProgressTime > refreshInterval || 0 == dwDownloadingCount)
        {
            if (m_cbProgress)
            {
                WINHTTP_PROGRESS_INFO progress = { 0 };
                progress.lfProgress = (double)ullDownloaded / (double)ullTotalLength;
                progress.ullCur = ullDownloaded;
                progress.ullTotal = ullTotalLength;
                progress.lfSpeed = lfSpeed;
                progress.activeThreads = dwDownloadingCount;
                progress.totalThreads = taskPackages.size();
                progress.costTime = curTime - startTime;
                progress.lfRemainTime = lfRemainTime;
                
                if (!m_cbProgress(progress))
                {
                    m_bAbort = true;
                }
            }

            lastProgressTime = curTime;
            if (0 == dwDownloadingCount)
            {
                fQuit = true;
            }
        }

        _SleepMillisecond(50);
    }
}

bool CWinHttpHelper::_PartialDownload(
    const _tstring& strUrl,
    const _tstring& strUrlFileName,
    const _tstring& strVerb, 
    LPVOID lpBuf,
    ULONGLONG ullContentLength,
    DWORD dwThreadCount
)
{
    //资源过小检查
    if (ullContentLength < WINHTTP_DOWNLOAD_MINIMUM_SIZE)
    {
        dwThreadCount = 1;
    }

    // 每个包大小
    ULONGLONG ullSinglePackageSize = ullContentLength / dwThreadCount;
    std::vector<std::thread> downloadThreads;

    // 保留容量, 防止发生空间分配导致迭代器失效
    std::vector<WINHTTP_PACKAGE_INFO> m_downloadPackages;
    m_downloadPackages.reserve(dwThreadCount);

    // 添加下载任务
    for (DWORD i = 0; i < dwThreadCount; i++)
    {
        ULONGLONG ullBeginPos = ullSinglePackageSize * i;
        ULONGLONG ullPackageSize = (i != dwThreadCount - 1) ? ullSinglePackageSize : ullContentLength - ullBeginPos;

        if (lpBuf)
        {
            m_downloadPackages.emplace_back(WINHTTP_PACKAGE_INFO(strUrl, _T(""), strVerb, (LPBYTE)lpBuf + ullBeginPos, ullBeginPos, ullPackageSize, i));
        }
        else
        {
            m_downloadPackages.emplace_back(WINHTTP_PACKAGE_INFO(strUrl, strUrlFileName, strVerb, nullptr, ullBeginPos, ullPackageSize, i));
        }

        downloadThreads.emplace_back(std::thread(
            [this, &m_downloadPackages, i]() {
                _PartialPackageTask(m_downloadPackages[i]);
            }));
    }

    // 创建进度报告线程
    std::thread processTask([this, &m_downloadPackages]() {
        _ProcessProcess(m_downloadPackages);
        }
    );

    processTask.join();

    // 等待所有下载线程结束
    for (auto &item : downloadThreads)
    {
        item.join();
    }

    // 获取每个线程的下载状态, 全部下载完成才算完成
    bool isAllFinish = std::all_of(m_downloadPackages.begin(), m_downloadPackages.end(), 
        [](const WINHTTP_PACKAGE_INFO& item) {
            return WINHTTP_PACKAGE_INFO::DownloadStatus::DS_DownloadFinish == item.dwStatus;
        });

    m_bAbort = false;
    return isAllFinish;
}

bool CWinHttpHelper::_PartialPackageTask(
    WINHTTP_PACKAGE_INFO& packInfo
)
{
    bool fResult = false;
    while (!m_bAbort)
    {
        packInfo.dwStatus = WINHTTP_PACKAGE_INFO::DownloadStatus::DS_Downloading;
        if (_DownloadPackage(&packInfo))
        {
            packInfo.dwStatus = WINHTTP_PACKAGE_INFO::DownloadStatus::DS_DownloadFinish;
            fResult = true;
            break;
        }

        packInfo.dwStatus = WINHTTP_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
    }

    if (!fResult)
    {
        m_bAbort = true;
        _tprintf(_T("[Fail] Task ID: %d\r\n"), packInfo.dwPartID);
    }
    else
    {
        //_tprintf(_T("[OK] Task ID: %d\r\n"), packInfo.dwPartID);
    }

    return fResult;
}

void CWinHttpHelper::_SleepMillisecond(int millisecond) const
{
    int span = 10;

    if (millisecond < span)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(span));
        return;
    }

    // 分段休眠, 防止中断休眠不及时
    clock_t tBegin = clock();
    while (!m_bAbort)
    {
        if (clock() - tBegin > millisecond)
        {
            break;
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(span));
    }
}

bool CWinHttpHelper::_DownloadPackage(LPWINHTTP_PACKAGE_INFO pPackageInfo)
{
    WINHTTP_URL_INFO urlInfo;
    HINTERNET hSession = NULL;
    HINTERNET hConnect = NULL;
    HINTERNET hRequest = NULL;
    bool bSuccess = false;

    // 分解URL
    if (!_CrackUrl(pPackageInfo->strUrl, &urlInfo))
    {
        return false;
    }

    _tstring strHeader = _GetRequestHeaderString();

    do
    {
        // 初始化应用程序对 WinHttp 函数的使用
        hSession = ::WinHttpOpen(
            WINHTTP_HTTPS_DOWNLOAD_AGENT, //指向字符串变量的指针,该变量包含调用 WinHTTP 函数的应用程序或实体的名称
            WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, //所需的访问类型
            WINHTTP_NO_PROXY_NAME, //代理访问时要使用的代理服务器的名称
            WINHTTP_NO_PROXY_BYPASS, //代理访问时要使用的代理服务器的密码
            WINHTTP_FLAG_SECURE_DEFAULTS // 指示影响此函数行为的各种选项的标志
        );

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

        // 打开给定站点的 HTTP 会话
        hConnect = ::WinHttpConnect(
            hSession,//由先前调用 WinHttpOpen 返回的有效 HINTERNETWinHTTP 会话句柄
            urlInfo.szHostName, //HTTP 服务器的主机名
            urlInfo.uc.nPort, //建立连接的服务器上的 TCP/IP 端口
            0 //保留参数, 必须为0
        );

        if (NULL == hConnect)
        {
            _PrintError(_T("InternetConnect"));
            break;
        }
        
        // 创建 HTTPS / HTTP 请求句柄
        DWORD dwFlags = (INTERNET_SCHEME_HTTPS == urlInfo.uc.nScheme) ? WINHTTP_FLAG_SECURE : 0;
        hRequest = ::WinHttpOpenRequest(
            hConnect, //WinHttpConnect 返回的 HTTP 会话的 HINTERNET 连接句柄
            _TStrToWStr(pPackageInfo->strVerb).c_str(), //请求的 HTTP 谓词
            urlInfo.szUrlPath, //指定 HTTP 谓词的目标资源名称
            NULL, //HTTP 版本的字符串的指针
            WINHTTP_NO_REFERER,//指定从中获取 请求 pwszObjectName 中的 URL 的文档的 URL
            WINHTTP_DEFAULT_ACCEPT_TYPES, //指定客户端接受的媒体类型
            dwFlags   //Internet 标志值
        );

        if (NULL == hRequest)
        {
            _PrintError(_T("HttpOpenRequest"));
            break;
        }

        // 设置请求文件数据范围
        if (!_SetRequestDataRange(hRequest, pPackageInfo->ullDataCurPos, pPackageInfo->ullDataEndPos, true))
        {
            _PrintError(_T("SetContentRange"));
            break;
        }

        // 设置超时
        BOOL fResult = ::WinHttpSetTimeouts(
            hRequest, 
            WINHTTP_RESOLVE_TIME_OUT, 
            WINHTTP_CONNECT_TIME_OUT, 
            WINHTTP_SEND_TIME_OUT, 
            WINHTTP_RECEIVE_TIME_OUT
        );

        // 发送请求
        for (int i = 0; i < 10 && !m_bAbort; i++)
        {
            if (_SendRequest(hRequest, strHeader, nullptr, 0))
            {
                fResult = true;
                break;
            }
        }

        if (!fResult)
        {
            _PrintError(_T("SendRequest"));
            break;
        }

        // 等待接收 WinHttpSendRequest 发起的 HTTP 请求的响应
        if (!::WinHttpReceiveResponse(hRequest, NULL))
        {
            _PrintError(_T("WinHttpReceiveResponse"));
            break;
        }

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

        // 读取数据到文件/缓冲
        if (!_InternetReadData(hRequest, pPackageInfo, NULL == pPackageInfo->lpBuffer))
        {
            _PrintError(_T("_InternetReadData"));
            break;
        }

        bSuccess = true;

    } while (false);

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

    return bSuccess;
}

bool CWinHttpHelper::_SetRequestDataRange(
    HINTERNET hRequest, 
    LONGLONG nBegin, 
    LONGLONG nEng, 
    bool isHasEnd
)
{
    WCHAR szBuf[MAX_PATH] = { 0 };

    if (isHasEnd)
    {
        (void)::StringCchPrintfW(szBuf, _countof(szBuf), L"Range:bytes=%lld-%lld", nBegin, nEng);
    }
    else
    {
        (void)::StringCchPrintfW(szBuf, _countof(szBuf), L"Range:bytes=%lld-", nBegin);
    }

    return ::WinHttpAddRequestHeaders(hRequest, szBuf, (DWORD)wcslen(szBuf), 0);
}

bool CWinHttpHelper::_InternetReadData(
    HINTERNET hRequest,
    LPWINHTTP_PACKAGE_INFO lpPackage,
    bool fFile/* = false*/,
    std::function<bool(const WINHTTP_PROGRESS_INFO& progress)> cbProgress/* = nullptr*/
)
{
    LARGE_INTEGER liDistanceToMove = { 0 };
    HANDLE hFile = INVALID_HANDLE_VALUE;
    bool bDownloadFinish = false;
    bool bRet = false;

    ULONGLONG ullCurRead = 0;
    ULONGLONG ullTotal = lpPackage->ullDataTotalSize;
    clock_t lastTime = ::clock();
    clock_t updateInterval = 100;

    LPBYTE lpBufPos = (LPBYTE)lpPackage->lpBuffer;
    lpPackage->dwStatus = WINHTTP_PACKAGE_INFO::DownloadStatus::DS_Downloading;
    std::vector<uint8_t> vData;

    do
    {
        if (fFile)
        {
            // 共享读写 创建/打开 文件, 多线程读写
            hFile = ::CreateFile(lpPackage->strFileName.c_str(),
                GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
                NULL,
                OPEN_ALWAYS,
                FILE_ATTRIBUTE_ARCHIVE,
                NULL);

            if (INVALID_HANDLE_VALUE == hFile)
            {
                _PrintError(_T("CreateFile"));
                m_bAbort = true;
                break;
            }

            // 设置数据写入位置
            liDistanceToMove.QuadPart = lpPackage->ullDataCurPos;
            ::SetFilePointerEx(hFile, liDistanceToMove, NULL, FILE_BEGIN);
        }

        do
        {
            // 检查可用数据
            DWORD dwAvailable = 0;
            DWORD dwRead = 0;
            if (!::WinHttpQueryDataAvailable(hRequest, &dwAvailable))
            {
                lpPackage->dwStatus = WINHTTP_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
                _PrintError(_T("WinHttpQueryDataAvailable"));

                if (ERROR_WINHTTP_TIMEOUT != ::GetLastError())
                {
                    m_bAbort = true;
                }

                break;
            }

            try
            {
                vData.resize(dwAvailable);
            }
            catch (...)
            {
                _PrintError(_T("std::vector resize"));
                break;
            }

            // 读取网络数据
            if (dwAvailable > 0)
            {
                bRet = ::WinHttpReadData(hRequest, vData.data(), dwAvailable, &dwRead);
                if (!bRet)
                {
                    lpPackage->dwStatus = WINHTTP_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
                    _PrintError(_T("WinHttpReadData"));
                    break;
                }

                // 写入到文件
                if (fFile)
                {
                    DWORD dwWritten = 0;
                    if (!::WriteFile(hFile, vData.data(), dwRead, &dwWritten, NULL))
                    {
                        lpPackage->dwStatus = WINHTTP_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
                        _PrintError(_T("WriteFile"));
                        break;
                    }
                }
                else
                {
                    // 写入到缓冲
                    memcpy(lpBufPos, vData.data(), dwRead);
                    lpBufPos += dwRead;
                }
            }

            // 记录当前线程数据位置
            lpPackage->ullDataCurPos += dwRead;
            lpPackage->ullDataCurSize += dwRead;

            clock_t curTime = ::clock();

            ullCurRead += dwRead;
            if (curTime - lastTime > updateInterval || 0 == dwRead)
            {
                lastTime = curTime;

                // 进度回调
                if (cbProgress)
                {
                    WINHTTP_PROGRESS_INFO progress = { 0 };
                    progress.lfProgress = (double)ullCurRead / (double)ullTotal;
                    progress.ullCur = ullCurRead;
                    progress.ullTotal = ullTotal;
                    
                    if (cbProgress(progress))
                    {
                        m_bAbort = true;
                    }
                }
            }

            // 检查下载是否完成
            if (!dwAvailable)
            {
                bDownloadFinish = true;
                lpPackage->dwStatus = WINHTTP_PACKAGE_INFO::DownloadStatus::DS_DownloadFinish;
                break;
            }

            // 中断下载检查
            if (m_bAbort)
            {
                lpPackage->dwStatus = WINHTTP_PACKAGE_INFO::DownloadStatus::DS_DownloadCancel;
                break;
            }

        } while (bRet && !m_bAbort);

    } while (false);

    if (INVALID_HANDLE_VALUE != hFile)
    {
        ::CloseHandle(hFile);
    }

    return bDownloadFinish;
}

bool CWinHttpHelper::_IsSupportAcceptRanges(HINTERNET hRequest)
{
    WCHAR szBuf[MAX_PATH] = { 0 };
    DWORD dwBufferLength = sizeof(szBuf);

    DWORD dwIndex = 0;

    if (!::WinHttpQueryHeaders(hRequest, 
        WINHTTP_QUERY_ACCEPT_RANGES, 
        WINHTTP_HEADER_NAME_BY_INDEX, 
        (LPVOID)szBuf, 
        &dwBufferLength, 
        &dwIndex)
        )
    {
        return false;
    }

    if (0 == _wcsicmp(szBuf, L"bytes"))
    {
        return true;
    }

    return false;
}

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

    // 查询资源大小
    if (!::WinHttpQueryHeaders(
        hRequest, 
        WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER64,
        WINHTTP_HEADER_NAME_BY_INDEX, //标头名称
        &ullContentLength, //接收信息的缓冲区
        &dwBufferLength, //数据缓冲区的长度
        NULL    //从零开始的标头索引的指针,用于枚举具有相同名称的多个标头
    ))
    {
        m_bHasSize = false;
        _PrintError(_T("QueryContentLength HttpQueryInfo"));
        *lpUllContentLength = 0;
        return false;
    }

    m_bHasSize = true;
    *lpUllContentLength = ullContentLength;

    return true;
}

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

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

    return dwRespCode;
}

bool CWinHttpHelper::_SendRequest(
    HINTERNET hRequest, 
    _tstring strHeader,
    LPVOID lpData, 
    DWORD dwSize
)
{
    std::wstring wstrHeader = _TStrToWStr(strHeader);
    LPCWSTR lpHeader = (LPCWSTR)wstrHeader.data();
    size_t dwHeaderSize = wstrHeader.size();

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

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

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

    // 没有客户端证书
    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 true;
    }

    return false;
}

bool CWinHttpHelper::_CrackUrl(
    const _tstring& strUrl, 
    LPWINHTTP_URL_INFO lpurlInfo
)
{
    // 将 URL 分解到其组件部件中
    if (!WinHttpCrackUrl(_TStrToWStr(strUrl).c_str(), 0, 0, &lpurlInfo->uc))
    {
        return false;
    }

    if (0 < ::wcslen(lpurlInfo->uc.lpszExtraInfo))
    {
        ::wcscat_s(lpurlInfo->uc.lpszUrlPath, _countof(lpurlInfo->szUrlPath), lpurlInfo->uc.lpszExtraInfo);
    }

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

    return true;
}

void CWinHttpHelper::_PrintError(LPCTSTR lpszError) const
{
    ::_tprintf(_T("[Error] %s LastError[%d] Tid: %d\n"), lpszError, ::GetLastError(), ::GetCurrentThreadId());
}

void CWinHttpHelper::_PrintWarn(LPCTSTR lpszError)  const
{
    ::_tprintf(_T("[Warn] %s LastError[%d] Tid: %d\n"), lpszError, ::GetLastError(), ::GetCurrentThreadId());
}

void CWinHttpHelper::_InternetStatusCallback(
    HINTERNET hSession, 
    DWORD_PTR dwContext, 
    DWORD dwInternetStatus, 
    LPVOID lpvStatusInformation, 
    DWORD dwStatusInformationLength
)
{
    UNREFERENCED_PARAMETER(hSession);
    UNREFERENCED_PARAMETER(dwContext);
    UNREFERENCED_PARAMETER(dwStatusInformationLength);

}

 main.cpp

#include "CDownloadFile.h"
#include "CWinHttpHelper.h"
#include "CWinHttp.h"
#include <locale>
#include <iostream>


#define GET_URL1  R"(https://gitee.com/flame_cyclone/fcassistant-binary/releases/download/1.0.1.15/LuaExample.lua)"
#define GET_URL2  R"(https://gitee.com/flame_cyclone/fcassistant-binary/releases/download/1.0.1.15/LuaExample.lua2)"

#define POST_URL  R"(https://gitee.com/flame_cyclone/fcassistant-binary/releases/download/1.0.1.15/LuaExample.lua)"
#define UPDATE_URL                           _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_LINK _T("https://cn.download.nvidia.com/Windows/561.09/561.09-desktop-win10-win11-64bit-international-dch-whql.exe")

#define TEST_LINK_AMD _T("https://drivers.amd.com/drivers/whql-amd-software-adrenalin-edition-24.8.1-win10-win11-aug-rdna.exe")

int main()
{
    setlocale(LC_ALL, "");

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

        std::string str;
        auto res = obj.Get(UPDATE_URL2, nullptr, 2);

        std::string vBuf;
        obj.DownloadToBuffer(
            TEST_LINK_AMD,
            vBuf,
            [](const WINHTTP_PROGRESS_INFO& progress) {
                _tprintf(_T("%d/%d cost: %.3lfs Progress: %.3lf%% %lld/%lld Speed: %.1lf Mbps %.1lfMB/s %.1lfKB/s RemainTime: %.1lfs\n"),
                progress.activeThreads,progress.totalThreads, (double)progress.costTime / 1000.0f,
                progress.lfProgress * 100, progress.ullCur, progress.ullTotal,
                    progress.lfSpeed / (1024.0f * 1024.0f) * 8.0f,
                    progress.lfSpeed / (1024.0f * 1024.0f),
                    progress.lfSpeed / (1024.0f), progress.lfRemainTime

                    );

                return true;
            }, 32
        );

        return 0;
    }

    return 0;
}


原文地址:https://blog.csdn.net/Flame_Cyclone/article/details/142579062

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!