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;
}