CWinInetHelper.h
#pragma once
#include <Windows.h>
#include <WinInet.h>
#include <string>
#include <vector>
#include <functional>
#include <tchar.h>
#include <map>
#include <set>
#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif
typedef struct _WININET_URL_INFO WININET_URL_INFO, *PWININET_URL_INFO;
typedef struct _WININET_PACKAGE_INFO WININET_PACKAGE_INFO, *PWININET_PACKAGE_INFO;
#define WININET_HTTPS_DOWNLOAD_AGENT _T(R"(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 WININET_MAX_CONNS_PER_SERVER (64) // 服务器最大连接数
#define WININET_DOWNLOAD_BLOCK_SIZE (1024 * 32) // 下载缓冲大小
#define WININET_DOWNLOAD_MINIMUM_SIZE (1024 * 4) // 单线程最小下载大小
#define WININET_PROGRESS_UPDATE_INTERVAL_TIME (1000) // 下载进度更新时间
#define WININET_SPEED_UPDATE_INTERVAL_TIME (1000) // 下载速度更新时间
#define WININET_FILE_BACKUP_MAX_CONUT (0) // 最多备份数量
class CWinInetResult
{
public:
CWinInetResult() :code(0) {}
std::string result; //响应结果
DWORD code; //响应状态码
};
// https下载类
class CWinInetHelper
{
public:
CWinInetHelper();
~CWinInetHelper();
//
// @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: 获取下载内容大小
// @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,
const _tstring& strFile,
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal, double lfSpeed)> cbProgress = nullptr,
DWORD dwThreadCount = 8
);
//
// @brief: 下载保存到缓存
// @param: strUrl URL链接
// @param: lpBuf 保存缓冲
// @param: dwBufSize 缓冲大小
// @param: cbProgress 下载进度回调函数
// @param: dwThreadCount 下载线程数
// @ret: bool 操作成功与否
bool DownloadToBuffer(
const _tstring& strUrl,
LPVOID lpBuf,
DWORD dwBufSize,
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal, double lfSpeed)> cbProgress = nullptr,
DWORD dwThreadCount = 8
);
//
// @brief: 发送Get请求
// @param: strUrl URL链接
// @param: strHeader 请求头
// @param: cbProgress 进度回调
// @ret: CHttpsResponse 响应结果
CWinInetResult Get(const _tstring& strUrl,
const _tstring& strHeader = _T("Accept: */*\r\nContent-Type:application/json;charset=UTF-8"),
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress = nullptr
);
//
// @brief: 发送Post请求
// @param: strUrl URL链接
// @param: strParam 请求参数
// @param: strHeader 请求头
// @param: cbProgress 进度回调
// @ret: CHttpsResponse 响应结果
CWinInetResult Post(const _tstring& strUrl,
std::string& strParam,
const _tstring& strHeader = _T("Accept: */*\r\nContent-Type:application/json;charset=UTF-8"),
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress = nullptr
);
private:
//
// @brief: 执行请求
// @param: strUrl URL链接
// @param: strMethod 请求方法
// @param: strParam 请求参数
// @param: strHeader 请求头
// @param: cbProgress 进度回调
// @ret: CWinHttpResponse 响应结果
CWinInetResult _DoSendRequest(
const _tstring& strUrl,
const _tstring& strMethod,
const std::string& strParam,
_tstring strHeader,
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress = nullptr
);
// 获取请求头字符串
_tstring _GetRequestHeaderString();
// 清空文件内容
void _TruncateFile(const _tstring& strPath);
// 下载前备份文件
bool _BackupFile(const _tstring& strPath);
// 进度处理
void _ProcessProcess(std::vector<WININET_PACKAGE_INFO>& taskPackages);
// 分段下载到文件
bool _PartialDownload(
const _tstring& strUrl,
const _tstring& strUrlFileName,
ULONGLONG ullContentLength,
DWORD dwThreadCount
);
// 分段下载到缓冲
bool _PartialDownload(
const _tstring& strUrl,
LPVOID lpBuf,
DWORD dwBufSize,
ULONGLONG ullContentLength,
DWORD dwThreadCount
);
// 分解链接
bool _CrackUrl(
const _tstring& strUrl,
PWININET_URL_INFO lpUci
);
// 查询是否支持接收范围
bool _IsSupportAcceptRanges(HINTERNET hRequest);
// 查询资源大小
bool _QueryContentLength(
HINTERNET hRequest,
PULONGLONG lpUllContentLength
);
// 获取状态码
long _GetStatusCode(HINTERNET hRequest);
// 发送请求
bool _SendRequest(
HINTERNET hRequest,
LPCTSTR lpszHeaders,
DWORD dwHeadersLength,
LPVOID lpData,
DWORD dwSize
);
// 读取网络流
bool _InternetReadData(
HINTERNET hRequest,
PWININET_PACKAGE_INFO lpDownloadPackage,
bool fFile = false,
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress = nullptr
);
// 设置请求数据范围
bool _SetRequestDataRange(
HINTERNET hRequest,
LONGLONG nBegin,
LONGLONG nEng,
bool isHasEnd = TRUE
);
// 下载分包
bool _DownloadPackage(PWININET_PACKAGE_INFO pPackageInfo);
void _SleepMillisecond(int millisecond) const;
// 打印警告
void _PrintWarn(LPCTSTR lpszError) const;
// 打印错误
void _PrintError(LPCTSTR lpszError) const;
// 异步回调函数(暂时未使用)
static void _InternetStatusCallback(
HINTERNET hInternet,
DWORD_PTR dwContext,
DWORD dwInternetStatus,
LPVOID lpvStatusInformation,
DWORD dwStatusInformationLength
);
private:
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal, double lfSpeed)> m_cbProgress;
std::map<_tstring, std::set<_tstring>> m_RequestHeader;
bool m_bAbort;
bool m_bSupportAcceptRanges;
bool m_bHasSize;
};
CWinInetHelper.cpp
#include "CWinInetHelper.h"
#include <strsafe.h>
#include <time.h>
#include <thread>
#include <algorithm>
#pragma comment(lib, "WinInet.lib")
const DWORD HTTP_REQUEST_FLAGS = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | // 禁用此特殊类型的重定向检测。 使用此标志时,WinINet 函数以透明方式允许从 HTTPS 重定向到 HTTP URL
INTERNET_FLAG_KEEP_CONNECTION | // 对连接使用保持活动语义(如果可用)
INTERNET_FLAG_NO_AUTH | // 不会自动尝试身份验证
INTERNET_FLAG_NO_COOKIES | // 不会自动向请求添加 Cookie 标头,也不会自动将返回的 Cookie 添加到 Cookie 数据库
INTERNET_FLAG_NO_UI | // 禁用 Cookie 对话框
INTERNET_FLAG_RELOAD; // 强制从源服务器下载请求的文件、对象或目录列表,而不是从缓存下载
const DWORD HTTPS_REQUEST_FLAGS = HTTP_REQUEST_FLAGS |
// HTTPS 设置
INTERNET_FLAG_SECURE | // 使用安全事务语义。 这转换为使用安全套接字层/专用通信技术 (SSL/PCT) ,并且仅在 HTTP 请求中有意义
INTERNET_FLAG_IGNORE_CERT_CN_INVALID | // 忽略X509 证书中的错误通用名称
SECURITY_FLAG_IGNORE_UNKNOWN_CA | // 忽略未知证书颁发机构问题
INTERNET_FLAG_IGNORE_CERT_DATE_INVALID; // 忽略过期的 X509 证书
// 下载分包信息
typedef struct _WININET_PACKAGE_INFO
{
enum DownloadStatus {
DS_Downloading = 0,
DS_DownloadFinish = 1,
DS_DownloadFailed = 2,
DS_DownloadCancel = 3,
};
_tstring strUrl;
_tstring strFileName;
LPVOID lpBuffer;
ULONGLONG ullDataCurPos;
ULONGLONG ullDataStartPos;
ULONGLONG ullContentLength;
ULONGLONG ullDownloaded;
DWORD dwStatus;
DWORD dwPartID;
_WININET_PACKAGE_INFO()
{
this->lpBuffer = NULL;
this->ullDataCurPos = 0;
this->ullDataStartPos = 0;
this->ullContentLength = 0;
this->ullDownloaded = 0;
this->dwStatus = DownloadStatus::DS_Downloading;
this->dwPartID = 0;
}
_WININET_PACKAGE_INFO(const _tstring& strUrl, const _tstring& strFileName, ULONGLONG ullBeginPos, ULONGLONG ullContentLength, DWORD dwPartID)
{
this->strUrl = strUrl;
this->strFileName = strFileName;
this->lpBuffer = NULL;
this->ullDataCurPos = ullBeginPos;
this->ullDataStartPos = ullBeginPos;
this->ullContentLength = ullContentLength;
this->ullDownloaded = 0;
this->dwPartID = dwPartID;
this->dwStatus = DownloadStatus::DS_Downloading;
}
_WININET_PACKAGE_INFO(const _tstring& strUrl, LPVOID lpBuffer, ULONGLONG ullBeginPos, ULONGLONG ullContentLength, DWORD dwPartID)
{
this->strUrl = strUrl;
this->lpBuffer = lpBuffer;
this->ullDataCurPos = ullBeginPos;
this->ullDataStartPos = ullBeginPos;
this->ullContentLength = ullContentLength;
this->ullDownloaded = 0;
this->dwPartID = dwPartID;
this->dwStatus = DownloadStatus::DS_Downloading;
}
}WININET_PACKAGE_INFO, *PWININET_PACKAGE_INFO;
typedef struct _WININET_URL_INFO
{
TCHAR szScheme[INTERNET_MAX_SCHEME_LENGTH];
TCHAR szHostName[INTERNET_MAX_HOST_NAME_LENGTH];
TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH];
TCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH];
TCHAR szUrlPath[INTERNET_MAX_URL_LENGTH];
TCHAR szExtraInfo[MAX_PATH];
URL_COMPONENTS uc = { 0 };
_WININET_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);
}
}WININET_URL_INFO, *PWININET_URL_INFO;
CWinInetHelper::CWinInetHelper()
:
m_bAbort(false),
m_bSupportAcceptRanges(false),
m_bHasSize(false)
{
}
CWinInetHelper::~CWinInetHelper()
{
}
void CWinInetHelper::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 CWinInetHelper::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);
}
}
}
_tstring CWinInetHelper::_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 CWinInetHelper::GetContentLength(
const _tstring& strUrl,
PULONGLONG lpUllContentLength
)
{
WININET_URL_INFO urlParts;
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
bool bSuccess = false;
if (strUrl.empty() || NULL == lpUllContentLength)
{
return false;
}
// 分解URL
if (!_CrackUrl(strUrl, &urlParts))
{
return false;
}
// 协议检查
if (!(INTERNET_SCHEME_HTTPS == urlParts.uc.nScheme || INTERNET_SCHEME_HTTP == urlParts.uc.nScheme))
{
_PrintError(_T("Scheme"));
return false;
}
_tstring strHeader = _GetRequestHeaderString();
do
{
// 初始化应用程序对 WinINet 函数的使用
hSession = ::InternetOpen(WININET_HTTPS_DOWNLOAD_AGENT, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (NULL == hSession)
{
_PrintError(_T("InternetOpen"));
break;
}
// 打开给定站点的 HTTP 会话
hConnect = ::InternetConnect(hSession, urlParts.szHostName, urlParts.uc.nPort, urlParts.szUserName, urlParts.szPassword, INTERNET_SERVICE_HTTP, 0, NULL);
if (NULL == hConnect)
{
_PrintError(_T("InternetConnect"));
break;
}
// 修改每个服务器的连接数
DWORD dwMaxConns = WININET_MAX_CONNS_PER_SERVER;
if (!::InternetSetOption(hConnect, INTERNET_OPTION_MAX_CONNS_PER_SERVER, (LPVOID)&dwMaxConns, sizeof(dwMaxConns)))
{
_PrintWarn(_T("InternetSetOption"));
}
if (!::InternetSetOption(NULL, INTERNET_OPTION_MAX_CONNS_PER_1_0_SERVER, (LPVOID)&dwMaxConns, sizeof(dwMaxConns)))
{
_PrintWarn(_T("InternetSetOption"));
}
// 创建 HTTPS / HTTP 请求句柄
DWORD dwFlags = (INTERNET_SCHEME_HTTPS == urlParts.uc.nScheme) ? HTTPS_REQUEST_FLAGS : HTTP_REQUEST_FLAGS;
hRequest = ::HttpOpenRequest(hConnect, _T("GET"), urlParts.szUrlPath, NULL, NULL, NULL, dwFlags, 0);
if (NULL == hRequest)
{
_PrintError(_T("HttpOpenRequest"));
break;
}
// 发送请求
if (!_SendRequest(hRequest, strHeader.data(), (DWORD)strHeader.size(), nullptr, 0))
{
_PrintError(_T("SendRequest"));
break;
}
DWORD statusCodes = _GetStatusCode(hRequest);
if (statusCodes < 200 || statusCodes >= 300)
{
char szResponseBuffer[MAX_PATH] = { 0 };
DWORD dwBufferLength = sizeof(szResponseBuffer);
::HttpQueryInfoA(hRequest, HTTP_QUERY_STATUS_TEXT, szResponseBuffer, &dwBufferLength, NULL);
char szErrorBuf[MAX_PATH] = { 0 };
StringCchPrintfA(szErrorBuf, _countof(szErrorBuf),
R"({"status":%d,"data":null,"message":"%d %s"})",
statusCodes, statusCodes, szResponseBuffer
);
printf("%s\r\n", szErrorBuf);
break;
}
// 记录是否支持接收范围请求
m_bSupportAcceptRanges = _IsSupportAcceptRanges(hRequest);
// 查询文件大小
if (!_QueryContentLength(hRequest, lpUllContentLength))
{
_PrintError(_T("QueryContentLength"));
break;
}
bSuccess = true;
} while (false);
// 释放资源
if (hRequest)
{
::InternetCloseHandle(hRequest);
}
if (hConnect)
{
::InternetCloseHandle(hConnect);
}
if (hSession)
{
::InternetCloseHandle(hSession);
}
return bSuccess;
}
bool CWinInetHelper::DownloadToFile(
const _tstring& strUrl,
const _tstring& strFile,
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal, double lfSpeed)> cbProgress,
DWORD dwThreadCount
)
{
ULONGLONG ullContentLength = 0;
_tstring strTmpFile = strFile + _T(".tmp");
if (strUrl.empty() || strFile.empty())
{
return false;
}
m_cbProgress = cbProgress;
//限制线程数量
dwThreadCount = dwThreadCount < 1 ? 1 : dwThreadCount;
dwThreadCount = dwThreadCount > 64 ? 64 : dwThreadCount;
// 获取资源大小
if (!GetContentLength(strUrl, &ullContentLength))
{
_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()))
{
if (ERROR_FILE_NOT_FOUND != ::GetLastError())
{
return false;
}
}
// 清空一下临时文件(如果存在的话)
(void)_TruncateFile(strTmpFile);
// 多线程分段下载
if (!_PartialDownload(strUrl, strTmpFile, ullContentLength, dwThreadCount))
{
_PrintError(_T("PartialDownload"));
return false;
}
// 下载完成则修改文件名
::MoveFile(strTmpFile.c_str(), strFile.c_str());
return true;
}
bool CWinInetHelper::DownloadToBuffer(
const _tstring& strUrl,
LPVOID lpBuf,
DWORD dwBufSize,
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal, double lfSpeed)> cbProgress,
DWORD dwThreadCount
)
{
ULONGLONG ullContentLength = 0;
if (strUrl.empty() || NULL == lpBuf || 0 == dwBufSize)
{
return false;
}
m_cbProgress = cbProgress;
//限制线程数量
dwThreadCount = dwThreadCount < 2 ? 2 : dwThreadCount;
dwThreadCount = dwThreadCount > 64 ? 64 : dwThreadCount;
// 获取资源大小
if (!GetContentLength(strUrl, &ullContentLength))
{
_PrintError(_T("GetContentLength"));
return false;
}
// 不支持范围请求则仅单线程下载
if (!m_bSupportAcceptRanges || !m_bHasSize)
{
dwThreadCount = 1;
}
// 多线程分段下载
if (!_PartialDownload(strUrl, lpBuf, dwBufSize, ullContentLength, dwThreadCount))
{
_PrintError(_T("PartialDownload"));
return false;
}
return true;
}
CWinInetResult CWinInetHelper::Get(
const _tstring& strUrl,
const _tstring& strHeader/* = _T("Content-Type:application/json;charset=UTF-8")*/,
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress/* = nullptr*/
)
{
return _DoSendRequest(strUrl, _T("GET"), "", strHeader, cbProgress);
}
CWinInetResult CWinInetHelper::Post(
const _tstring& strUrl,
std::string& strParam,
const _tstring& strHeader,
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress/* = nullptr*/
)
{
return _DoSendRequest(strUrl, _T("POST"), strParam, strHeader, cbProgress);
}
CWinInetResult CWinInetHelper::_DoSendRequest(
const _tstring& strUrl,
const _tstring& strMethod,
const std::string& strParam,
_tstring strHeader,
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress/* = nullptr*/
)
{
CWinInetResult httpsResponse;
WININET_URL_INFO urlParts;
ULONGLONG ullContentLength = 0;
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
bool bSuccess = false;
// 分解URL
if (!_CrackUrl(strUrl, &urlParts))
{
return httpsResponse;
}
if (strHeader.empty())
{
strHeader = _GetRequestHeaderString();
}
do
{
// 初始化应用程序对 WinINet 函数的使用
hSession = ::InternetOpen(WININET_HTTPS_DOWNLOAD_AGENT, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (NULL == hSession)
{
_PrintError(_T("InternetOpen"));
break;
}
// 打开给定站点的 HTTP 会话
hConnect = ::InternetConnect(hSession, urlParts.szHostName, urlParts.uc.nPort, urlParts.szUserName, urlParts.szPassword, INTERNET_SERVICE_HTTP, 0, NULL);
if (NULL == hConnect)
{
_PrintError(_T("InternetConnect"));
break;
}
// 创建 HTTPS / HTTP 请求句柄
DWORD dwFlags = (INTERNET_SCHEME_HTTPS == urlParts.uc.nScheme) ? HTTPS_REQUEST_FLAGS : HTTP_REQUEST_FLAGS;
hRequest = ::HttpOpenRequest(hConnect, strMethod.c_str(), urlParts.szUrlPath, NULL, NULL, NULL, dwFlags, 0);
if (NULL == hRequest)
{
_PrintError(_T("HttpOpenRequest"));
break;
}
// 发送请求
if (!_SendRequest(hRequest, strHeader.data(), (DWORD)strHeader.size(), (LPVOID)strParam.data(), (DWORD)strParam.size()))
{
_PrintError(_T("SendRequest"));
break;
}
// 记录状态码
httpsResponse.code = _GetStatusCode(hRequest);
// 查询文件大小
if (!_QueryContentLength(hRequest, &ullContentLength))
{
_PrintError(_T("QueryContentLength"));
break;
}
WININET_PACKAGE_INFO packageInfo;
httpsResponse.result.resize(ullContentLength);
packageInfo.lpBuffer = (LPVOID)httpsResponse.result.data();
packageInfo.ullContentLength = ullContentLength;
if (!_InternetReadData(hRequest, &packageInfo, false, cbProgress))
{
_PrintError(_T("SaveToBuffer"));
break;
}
bSuccess = true;
} while (false);
// 释放资源
if (hRequest)
{
::InternetCloseHandle(hRequest);
}
if (hConnect)
{
::InternetCloseHandle(hConnect);
}
if (hSession)
{
::InternetCloseHandle(hSession);
}
return httpsResponse;
}
void CWinInetHelper::_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 CWinInetHelper::_BackupFile(const _tstring& strPath)
{
TCHAR szBuf[MAX_PATH] = { 0 };
_tstring strBakFile;
bool isBackupOk = false;
if (0 == WININET_FILE_BACKUP_MAX_CONUT)
{
return true;
}
for (int i = 1; i <= WININET_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 < WININET_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"), WININET_FILE_BACKUP_MAX_CONUT);
strBakFile = strPath + _T(".") + szBuf + _T(".bak");
if (::MoveFile(strPath.c_str(), strBakFile.c_str()))
{
isBackupOk = true;
}
}
return isBackupOk;
}
void CWinInetHelper::_ProcessProcess(std::vector<WININET_PACKAGE_INFO>& taskPackages)
{
ULONGLONG ullLastDownload = 0;
clock_t refreshInterval = WININET_PROGRESS_UPDATE_INTERVAL_TIME;
clock_t lastTime = ::clock();
clock_t lastProgressTime = ::clock();
double lfSpeed = 0.0f;
clock_t speedTime = WININET_SPEED_UPDATE_INTERVAL_TIME;
bool fQuit = false;
// 等待所有线程结束下载
while (!fQuit)
{
ULONGLONG ullDownloaded = 0;
ULONGLONG ullTotalLength = 0;
DWORD dwDownloadingCount = 0;
// 统计已下载量 与 总下载量
for (const auto& item : taskPackages)
{
ullDownloaded += item.ullDownloaded;
ullTotalLength += item.ullContentLength;
if (WININET_PACKAGE_INFO::DownloadStatus::DS_Downloading == item.dwStatus)
{
dwDownloadingCount++;
}
}
clock_t curTime = ::clock();
if (curTime - lastTime >= speedTime)
{
lfSpeed = (double)(ullDownloaded - ullLastDownload) / ((double)speedTime / 1000.0f);
lastTime = curTime;
ullLastDownload = ullDownloaded;
}
// 进度报告
if (curTime - lastProgressTime > refreshInterval || 0 == dwDownloadingCount)
{
// 进度回调
if (m_cbProgress)
{
double lfProgress = (double)ullDownloaded / (double)ullTotalLength;
m_cbProgress(lfProgress, ullDownloaded, ullTotalLength, lfSpeed);
}
lastProgressTime = curTime;
if (0 == dwDownloadingCount)
{
fQuit = true;
}
}
_SleepMillisecond(50);
}
}
bool CWinInetHelper::_PartialDownload(
const _tstring& strUrl,
const _tstring& strUrlFileName,
ULONGLONG ullContentLength,
DWORD dwThreadCount
)
{
//资源过小检查
if (ullContentLength < WININET_DOWNLOAD_MINIMUM_SIZE)
{
dwThreadCount = 1;
}
// 每个包大小
ULONGLONG ullSinglePackageSize = ullContentLength / dwThreadCount;
std::vector<std::thread> downloadThreads;
// 保留容量, 防止发生空间分配导致迭代器失效
std::vector<WININET_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;
m_downloadPackages.emplace_back(WININET_PACKAGE_INFO(strUrl, strUrlFileName, ullBeginPos, ullPackageSize, i));
downloadThreads.emplace_back(std::thread(
[this, &m_downloadPackages, i]() {
if (!_DownloadPackage(&m_downloadPackages[i]))
{
m_downloadPackages[i].dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
}
}));
}
// 创建进度报告线程
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 WININET_PACKAGE_INFO& item) {
return WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFinish == item.dwStatus;
});
return isAllFinish;
}
bool CWinInetHelper::_PartialDownload(
const _tstring& strUrl,
LPVOID lpBuf,
DWORD dwBufSize,
ULONGLONG ullContentLength,
DWORD dwThreadCount
)
{
//资源过小检查
if (ullContentLength < WININET_DOWNLOAD_MINIMUM_SIZE || dwBufSize < WININET_DOWNLOAD_MINIMUM_SIZE)
{
dwThreadCount = 1;
}
// 每个包大小
ullContentLength = ullContentLength < dwBufSize ? ullContentLength : dwBufSize;
ULONGLONG ullSinglePackageSize = ullContentLength / dwThreadCount;
std::vector<std::thread> downloadThreads;
// 保留容量, 防止发生空间分配导致迭代器失效
std::vector<WININET_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;
m_downloadPackages.emplace_back(WININET_PACKAGE_INFO(strUrl, (LPBYTE)lpBuf + ullBeginPos, ullBeginPos, ullPackageSize, i));
downloadThreads.emplace_back(std::thread(
[this, &m_downloadPackages, i]() {
if (!_DownloadPackage(&m_downloadPackages[i]))
{
m_downloadPackages[i].dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
}
}));
}
// 创建进度报告线程
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 WININET_PACKAGE_INFO& item) {
return WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFinish == item.dwStatus;
});
return isAllFinish;
}
void CWinInetHelper::_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 CWinInetHelper::_DownloadPackage(PWININET_PACKAGE_INFO pPackageInfo)
{
WININET_URL_INFO urlParts;
ULONGLONG ullContentLength = 0;
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
bool bSuccess = false;
// 分解URL
if (!_CrackUrl(pPackageInfo->strUrl, &urlParts))
{
return false;
}
_tstring strHeader = _GetRequestHeaderString();
do
{
// 初始化应用程序对 WinINet 函数的使用
hSession = ::InternetOpen(WININET_HTTPS_DOWNLOAD_AGENT, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (NULL == hSession)
{
_PrintError(_T("InternetOpen"));
break;
}
// 打开给定站点的 HTTP 会话
hConnect = ::InternetConnect(hSession, urlParts.szHostName, urlParts.uc.nPort, urlParts.szUserName, urlParts.szPassword, INTERNET_SERVICE_HTTP, 0, NULL);
if (NULL == hConnect)
{
_PrintError(_T("InternetConnect"));
break;
}
// 创建 HTTPS / HTTP 请求句柄
DWORD dwFlags = (INTERNET_SCHEME_HTTPS == urlParts.uc.nScheme) ? HTTPS_REQUEST_FLAGS : HTTP_REQUEST_FLAGS;
hRequest = ::HttpOpenRequest(hConnect, _T("GET"), urlParts.szUrlPath, NULL, NULL, NULL, dwFlags, 0);
if (NULL == hRequest)
{
_PrintError(_T("HttpOpenRequest"));
break;
}
// 设置请求文件数据范围
ULONGLONG ullStart = pPackageInfo->ullDataCurPos;
ULONGLONG ullEnd = pPackageInfo->ullDataCurPos;
if (pPackageInfo->ullContentLength > 0)
{
ullEnd = pPackageInfo->ullDataStartPos + pPackageInfo->ullContentLength - 1;
}
if (!_SetRequestDataRange(hRequest, ullStart, ullEnd, true))
{
_PrintError(_T("SetContentRange"));
break;
}
// 发送请求
if (!_SendRequest(hRequest, strHeader.data(), (DWORD)strHeader.size(), nullptr, 0))
{
_PrintError(_T("SendRequest"));
break;
}
// 查询文件大小
if (!_QueryContentLength(hRequest, &ullContentLength))
{
_PrintError(_T("QueryContentLength"));
break;
}
if (NULL != pPackageInfo->lpBuffer)
{
if (!_InternetReadData(hRequest, pPackageInfo, false))
{
_PrintError(_T("SaveToBuffer"));
break;
}
}
else
{
if (!_InternetReadData(hRequest, pPackageInfo, true))
{
_PrintError(_T("SaveToFile"));
break;
}
}
bSuccess = true;
} while (false);
// 释放资源
if (hRequest)
{
::InternetCloseHandle(hRequest);
}
if (hConnect)
{
::InternetCloseHandle(hConnect);
}
if (hSession)
{
::InternetCloseHandle(hSession);
}
return bSuccess;
}
bool CWinInetHelper::_SetRequestDataRange(
HINTERNET hRequest,
LONGLONG nBegin,
LONGLONG nEng,
bool isHasEnd
)
{
TCHAR szBuf[MAX_PATH] = { 0 };
if (isHasEnd)
{
(void)::StringCchPrintf(szBuf, _countof(szBuf), _T("Range:bytes=%lld-%lld"), nBegin, nEng);
}
else
{
(void)::StringCchPrintf(szBuf, _countof(szBuf), _T("Range:bytes=%lld-"), nBegin);
}
return ::HttpAddRequestHeaders(hRequest, szBuf, (DWORD)_tcslen(szBuf), 0);
}
bool CWinInetHelper::_InternetReadData(
HINTERNET hRequest,
PWININET_PACKAGE_INFO lpDownloadPackage,
bool fFile/* = false*/,
std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress/* = nullptr*/
)
{
LPBYTE lpBuffer = NULL;
LPBYTE lpBufPos = (LPBYTE)lpDownloadPackage->lpBuffer;
LARGE_INTEGER liDistanceToMove = { 0 };
HANDLE hFile = INVALID_HANDLE_VALUE;
bool bDownloadFinish = false;
bool bRet = false;
ULONGLONG ullCurRead = 0;
ULONGLONG ullTotal = lpDownloadPackage->ullContentLength;
clock_t lastTime = ::clock();
clock_t updateInterval = 100;
do
{
if (fFile)
{
// 共享读写 创建/打开 文件, 多线程读写
hFile = ::CreateFile(lpDownloadPackage->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"));
break;
}
}
// 分配下载缓冲
lpBuffer = (LPBYTE)::HeapAlloc(::GetProcessHeap(), 0, WININET_DOWNLOAD_BLOCK_SIZE);
if (NULL == lpBuffer)
{
_PrintError(_T("HeapAlloc"));
break;
}
liDistanceToMove.QuadPart = lpDownloadPackage->ullDataStartPos;
::SetFilePointerEx(hFile, liDistanceToMove, NULL, FILE_BEGIN);
do
{
// 读取下载的数据到缓冲区
DWORD dwRead = 0;
bRet = ::InternetReadFile(hRequest, lpBuffer, WININET_DOWNLOAD_BLOCK_SIZE, &dwRead);
if (false == bRet)
{
lpDownloadPackage->dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
_PrintError(_T("InternetReadFile"));
break;
}
clock_t curTime = ::clock();
ullCurRead += dwRead;
if (curTime - lastTime > updateInterval || 0 == dwRead)
{
lastTime = curTime;
// 进度回调
if (cbProgress)
{
cbProgress((double)ullCurRead / (double)ullTotal, ullCurRead, ullTotal);
}
}
// 检查下载是否完成
if (0 == dwRead)
{
bDownloadFinish = true;
lpDownloadPackage->dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFinish;
break;
}
if (fFile)
{
// 写入到文件
DWORD dwWritten = 0;
if (!::WriteFile(hFile, lpBuffer, dwRead, &dwWritten, NULL))
{
lpDownloadPackage->dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
_PrintError(_T("WriteFile"));
break;
}
// 记录当前线程已下载数据量
lpDownloadPackage->ullDownloaded += dwWritten;
}
else
{
// 写入到缓冲
memcpy(lpBufPos, lpBuffer, dwRead);
lpBufPos += dwRead;
lpDownloadPackage->ullDownloaded += dwRead;
}
// 中断下载检查
if (m_bAbort)
{
lpDownloadPackage->dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadCancel;
break;
}
} while (bRet);
} while (false);
if (INVALID_HANDLE_VALUE != hFile)
{
::CloseHandle(hFile);
}
if (lpBuffer)
{
::HeapFree(::GetProcessHeap(), 0, lpBuffer);
}
return bDownloadFinish;
}
bool CWinInetHelper::_IsSupportAcceptRanges(HINTERNET hRequest)
{
TCHAR szResponseBuffer[MAX_PATH] = { 0 };
DWORD dwBufferLength = sizeof(szResponseBuffer);
// 查询资源大小
if (!::HttpQueryInfo(hRequest, HTTP_QUERY_ACCEPT_RANGES, szResponseBuffer, &dwBufferLength, NULL))
{
_PrintError(_T("_IsSupportAcceptRanges HttpQueryInfo"));
return false;
}
if (0 == _tcsicmp(szResponseBuffer, _T("bytes")))
{
return true;
}
return false;
}
bool CWinInetHelper::_QueryContentLength(
HINTERNET hRequest,
PULONGLONG lpUllContentLength
)
{
TCHAR szResponseBuffer[MAX_PATH] = { 0 };
DWORD dwBufferLength = sizeof(szResponseBuffer);
// 查询资源大小
if (!::HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH, szResponseBuffer, &dwBufferLength, NULL))
{
m_bHasSize = false;
_PrintError(_T("QueryContentLength HttpQueryInfo"));
*lpUllContentLength = 0;
return false;
}
m_bHasSize = true;
*lpUllContentLength = _tcstoll(szResponseBuffer, NULL, 10);
return true;
}
long CWinInetHelper::_GetStatusCode(HINTERNET hRequest)
{
DWORD dwRespCode = 0;
DWORD dwSize = sizeof(dwRespCode);
// 查询请求状态码
if (!::HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &dwRespCode, &dwSize, NULL))
{
return dwRespCode;
}
return dwRespCode;
}
bool CWinInetHelper::_SendRequest(
HINTERNET hRequest,
LPCTSTR lpszHeaders,
DWORD dwHeadersLength,
LPVOID lpData,
DWORD dwSize
)
{
DWORD dwFlags = 0;
DWORD dwFlagsSize = sizeof(dwFlags);
dwFlags = INTERNET_FLAG_NO_AUTO_REDIRECT;
if (!::InternetSetOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags)))
{
_PrintError(_T("InternetSetOption"));
return false;
}
// 将指定的请求发送到 HTTP 服务器
if (::HttpSendRequest(hRequest, lpszHeaders, dwHeadersLength, lpData, dwSize))
{
return true;
}
// 检查错误是否为未知证书颁发机构
if (ERROR_INTERNET_INVALID_CA != ::GetLastError())
{
return false;
}
// 获取INTERNET_OPTION_SECURITY_FLAGS标志
if (!::InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, &dwFlagsSize))
{
_PrintError(_T("InternetQueryOption"));
return false;
}
// 忽略未知证书颁发机构问题。
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
if (!::InternetSetOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags)))
{
_PrintError(_T("InternetSetOption"));
return false;
}
// 再次发送请求
return ::HttpSendRequest(hRequest, lpszHeaders, dwHeadersLength, lpData, dwSize);
}
bool CWinInetHelper::_CrackUrl(
const _tstring& strUrl,
PWININET_URL_INFO lpUrlParts
)
{
// 将 URL 分解到其组件部件中
if (!::InternetCrackUrl(strUrl.c_str(), 0, 0, &lpUrlParts->uc))
{
_PrintError(_T("InternetCrackUrl"));
return false;
}
if (0 < ::_tcslen(lpUrlParts->uc.lpszExtraInfo))
{
::_tcscat_s(lpUrlParts->uc.lpszUrlPath, _countof(lpUrlParts->szUrlPath), lpUrlParts->uc.lpszExtraInfo);
}
return true;
}
void CWinInetHelper::_PrintError(LPCTSTR lpszError) const
{
::_tprintf(_T("[Error] %s LastError[%d]\n"), lpszError, ::GetLastError());
}
void CWinInetHelper::_PrintWarn(LPCTSTR lpszError) const
{
::_tprintf(_T("[Warn] %s LastError[%d]\n"), lpszError, ::GetLastError());
}
void CWinInetHelper::_InternetStatusCallback(
HINTERNET hSession,
DWORD_PTR dwContext,
DWORD dwInternetStatus,
LPVOID lpvStatusInformation,
DWORD dwStatusInformationLength
)
{
UNREFERENCED_PARAMETER(hSession);
UNREFERENCED_PARAMETER(dwContext);
UNREFERENCED_PARAMETER(dwStatusInformationLength);
switch (dwInternetStatus)
{
//由 InternetConnect 用来指示它已创建新句柄。
case INTERNET_STATUS_HANDLE_CREATED:
{
}
break;
// 此句柄值已终止。 pvStatusInformation 包含正在关闭的句柄的地址。 lpvStatusInformation 参数包含正在关闭的句柄的地址。
case INTERNET_STATUS_HANDLE_CLOSING:
{
}
break;
// 异步操作已完成。 lpvStatusInformation 参数包含INTERNET_ASYNC_RESULT结构的地址。
case INTERNET_STATUS_REQUEST_COMPLETE:
{
if (ERROR_SUCCESS == ((LPINTERNET_ASYNC_RESULT)
(lpvStatusInformation))->dwError)
{
}
else
{
}
}
break;
}
}