CWinHttpClient.h
#pragma once
#include <Windows.h>
#include <WinHttp.h>
#include <stdint.h>
#include <string>
#include <vector>
#include <functional>
#include <map>
#include <set>
#include <thread>
#include <time.h>
#include <tchar.h>
#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif
#pragma comment(lib, "winhttp.lib")
#define WINHTTP_AGENT_HTTPS "(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0)"
// 响应结果
class CWinHttpResult
{
public:
CWinHttpResult() :code(0) {}
std::string result; //响应结果
uint32_t code; //响应状态码
};
// 进度信息
typedef struct _WINHTTP_PROGRESS_INFO
{
ULONGLONG ullCur; // 当前下载量(字节)
ULONGLONG ullTotal; // 总数据量(字节)
double lfProgress; // 当前进度(0.0f - 1.0f)
double lfSpeed; // 当前速度(字节/秒)
double lfRemainTime; // 剩余时间(毫秒)
clock_t costTime; // 消耗时间(毫秒)
uint32_t nActiveThread; // 活动任务数
uint32_t nTotalThread; // 总任务线程数
}WINHTTP_PROGRESS_INFO, *LPWINHTTP_PROGRESS_INFO;
// 异步数据
typedef struct _WHTTP_ASYNC_DATA
{
DWORD_PTR dwContext; // 上下文
HANDLE hEvent; // 事件
DWORD dwWait; // 事件等待结果
DWORD dwSize; // 传输数据大小
WINHTTP_ASYNC_RESULT AsyncResult; // 异步结果
}WHTTP_ASYNC_DATA, *LPWHTTP_ASYNC_DATA;
// 请求头信息
using WinHttpRequestHeader = std::map<_tstring, std::set<_tstring>>;
// 进度回调
using WinHttpProgressCallback = std::function<bool(const WINHTTP_PROGRESS_INFO& progress)>;
// WinHttp客户端辅助类
class CWinHttpClient
{
public:
CWinHttpClient();
~CWinHttpClient();
// 关闭
void Close();
// 发送 GET 请求
CWinHttpResult Get(const _tstring& strUrl, WinHttpProgressCallback cb = nullptr);
// 发送 POST 请求
CWinHttpResult Post(const _tstring& strUrl, const std::string& strParam, WinHttpProgressCallback cb = nullptr);
// 发送 GET 请求(多线程)
CWinHttpResult GetEx(const _tstring& strUrl, WinHttpProgressCallback cb = nullptr, DWORD dwThreadCount = 1);
// 多线程下载文件
bool DownloadFile(const _tstring& strUrl, const _tstring& strFile = _T(""), WinHttpProgressCallback cb = nullptr, DWORD dwThreadCount = 4);
// 添加请求头信息
void AddRequestHeader(const _tstring strCaption, const _tstring strData);
// 移除请求头信息
void RemoveRequestHeader(const _tstring strCaption, const _tstring strData);
// 清除请求头信息
void ClearRequestHeader();
// 设置用户代理字符串
void SetAgent(const _tstring strAgent = _T(WINHTTP_AGENT_HTTPS));
// 设置请求数据范围
void SetReQuestDataRange(ULONGLONG ullStart = 0, ULONGLONG ullLength = -1);
// 设置异步状态打印
void SetPrintStatus(bool fEnable = true);
public:
// 字符串转UTF-8编码字符串
static std::string TStrToU8Str(const _tstring& str);
// 字符串转宽字节字符串
static std::wstring TStrToWStr(const _tstring& str);
// 宽字节字符串转字符串
static _tstring WStrToTStr(const std::wstring& str);
// 控制台打印
static void ConsoleOutput(LPCTSTR pFormat, ...);
private:
// 获取请求头字符串
_tstring _GetRequestHeaderString();
// 执行请求
CWinHttpResult _DoRequest(const _tstring& strUrl, const _tstring& strMethod, const std::string& strParam);
// 执行请求(多线程)
CWinHttpResult _GetEx(const _tstring& strUrl, WinHttpProgressCallback cb = nullptr, DWORD dwThreadCount = 1);
CWinHttpResult _MultiThreadRequest(const _tstring& strUrl, ULONGLONG ullContentLength, DWORD dwThreadCount = 1);
// 读取网络流
bool _InternetReadData(std::string& strData);
// 查询资源大小
bool _QueryContentLength(PULONGLONG lpUllContentLength);
// 查询是否支持接收范围
bool _IsSupportAcceptRanges();
// 设置请求数据范围
bool _SetRequestDataRange(LONGLONG nBegin, LONGLONG nLength);
// 获取状态码
DWORD _GetStatusCode(HINTERNET hRequest);
// 错误输出
void _PrintError(LPCTSTR lpszError) const;
// 状态码打印
static void _PrintStatus(DWORD dwCode, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength);
// 状态回调
static VOID CALLBACK WinHttpStatusCallback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwStatus, LPVOID lpvInfo, DWORD dwInfoLength);
// 状态回调
void _WinHttpStatusCallback(HINTERNET hInternet, DWORD dwStatus, LPVOID lpvInfo, DWORD dwInfoLength);
// 等待异步事件
bool _WaitForAsyncEvent(DWORD dwMilliseconds = INFINITE);
// 打开会话
bool _WinHttpOpen(LPCWSTR pszAgentW, DWORD dwAccessType, LPCWSTR pszProxyW, LPCWSTR pszProxyBypassW, DWORD dwFlags);
// 设置会话超时
bool _WinHttpSetSessionTimeouts(int nResolveTimeout, int nConnectTimeout,int nSendTimeout,int nReceiveTimeout);
// 设置请求超时
bool _WinHttpSetRequestTimeouts(int nResolveTimeout, int nConnectTimeout,int nSendTimeout,int nReceiveTimeout);
// 设置状态回调
bool _WinHttpSetStatusCallback(WINHTTP_STATUS_CALLBACK lpfnInternetCallback, DWORD dwNotificationFlags);
// 设置会话选项
bool _WinHttpSetSessionOption(DWORD dwOption, LPVOID lpBuffer, DWORD dwBufferLength);
// 设置请求选项
bool _WinHttpSetRequestOption(DWORD dwOption, LPVOID lpBuffer, DWORD dwBufferLength);
// 连接会话
bool _WinHttpConnect(LPCWSTR pswzServerName, INTERNET_PORT nServerPort);
// 打开请求
bool _WinHttpOpenRequest(const _tstring& strVerb, LPCWSTR pwszObjectName, LPCWSTR pwszVersion, LPCWSTR pwszReferrer, LPCWSTR *ppwszAcceptTypes, DWORD dwFlags);
// 发送请求
bool _WinHttpSendRequest(_tstring strHeader, LPVOID lpData, DWORD dwSize, DWORD_PTR dwContext);
// 查询请求头
bool _WinHttpQueryHeaders(DWORD dwInfoLevel, LPCWSTR pwszName, LPVOID lpBuffer, LPDWORD lpdwBufferLength, LPDWORD lpdwIndex);
// 接收响应数据
bool _WinHttpReceiveResponse(HINTERNET hRequest);
// 查询请求数据是否可用
bool _WinHttpQueryDataAvailable(LPDWORD lpdwNumberOfBytesAvailable);
// 读取请求数据
bool _WinHttpReadData(LPVOID lpBuffer, DWORD dwNumberOfBytesToRead, LPDWORD lpdwNumberOfBytesRead);
// 宽字节字符串转多字节字符串
static std::string _WStrToMultiStr(UINT CodePage, const std::wstring& str);
// 多字节字符串转宽字节字符串
static std::wstring _MultiStrToWStr(UINT CodePage, const std::string& str);
private:
HINTERNET m_hSession; // 会话句柄
HINTERNET m_hConnect; // 连接句柄
HINTERNET m_hRequest; // 请求句柄
WinHttpRequestHeader m_RequestHeader; // 请求头
WinHttpProgressCallback m_cbProgress; // 进度回调
WHTTP_ASYNC_DATA m_AsyncData; // 异步信息
_tstring m_strAgent; // 代理字符串
_tstring m_strFilePath; // 保存文件路径
ULONGLONG m_ullStart; // 请求数据起始位置
ULONGLONG m_ullLength; // 请求长度
ULONGLONG m_ullDownload; // 已下载数据
bool m_fAbort; // 终止
bool m_fPrint; // 打印进度
};
CWinHttpClient.cpp
#include "CWinHttpClient.h"
#include <strsafe.h>
#include <thread>
#include <atomic>
typedef struct _WINHTTP_URL_INFO
{
std::wstring strScheme;
std::wstring strHostName;
std::wstring strUserName;
std::wstring strPassword;
std::wstring strUrlPath;
std::wstring strExtraInfo;
URL_COMPONENTS uc = { 0 };
_WINHTTP_URL_INFO()
{
memset(&uc, 0, sizeof(uc));
try
{
strScheme.resize(32);
strHostName.resize(128);
strUserName.resize(128);
strPassword.resize(128);
strUrlPath.resize(2048);
strExtraInfo.resize(512);
this->uc.dwStructSize = sizeof(this->uc);
this->uc.lpszScheme = &this->strScheme[0];
this->uc.dwSchemeLength = (DWORD)strScheme.size();
this->uc.lpszHostName = &this->strHostName[0];
this->uc.dwHostNameLength = (DWORD)strHostName.size();
this->uc.lpszUserName = &this->strUserName[0];
this->uc.dwUserNameLength = (DWORD)strUserName.size();
this->uc.lpszPassword = &this->strPassword[0];
this->uc.dwPasswordLength = (DWORD)strPassword.size();
this->uc.lpszUrlPath = &this->strUrlPath[0];
this->uc.dwUrlPathLength = (DWORD)strUrlPath.size();
this->uc.lpszExtraInfo = &this->strExtraInfo[0];
this->uc.dwExtraInfoLength = (DWORD)strExtraInfo.size();
}
catch (...)
{
}
}
}WINHTTP_URL_INFO, *PWINHTTP_URL_INFO;
CWinHttpClient::CWinHttpClient()
:
m_hSession(NULL),
m_hConnect(NULL),
m_hRequest(NULL),
m_fAbort(false),
m_fPrint(false),
m_ullStart(0),
m_ullLength(-1),
m_ullDownload(0),
m_AsyncData{ 0 }
{
m_AsyncData.hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
}
CWinHttpClient::~CWinHttpClient()
{
Close();
if (NULL != m_AsyncData.hEvent)
{
::CloseHandle(m_AsyncData.hEvent);
}
}
void CWinHttpClient::Close()
{
m_fAbort = true;
if (NULL != m_AsyncData.hEvent)
{
::SetEvent(m_AsyncData.hEvent);
}
if (m_hRequest)
{
::WinHttpCloseHandle(m_hRequest);
m_hRequest = NULL;
}
if (m_hConnect)
{
::WinHttpCloseHandle(m_hConnect);
m_hConnect = NULL;
}
if (m_hSession)
{
::WinHttpCloseHandle(m_hSession);
m_hSession = NULL;
}
m_fAbort = false;
}
static bool _CrackUrl(const _tstring& strUrl, PWINHTTP_URL_INFO lpUrlInfo)
{
// 将 URL 分解到其组件部件中
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpcrackurl
// 即使在异步模式下使用 WinHTTP (即在 WinHttpOpen) 中设置了WINHTTP_FLAG_ASYNC时,此函数也会同步运行。
if (!::WinHttpCrackUrl(CWinHttpClient::TStrToWStr(strUrl).c_str(), 0, 0, &lpUrlInfo->uc))
{
return false;
}
lpUrlInfo->strScheme.resize(wcslen(lpUrlInfo->strScheme.c_str()));
lpUrlInfo->strExtraInfo.resize(wcslen(lpUrlInfo->strExtraInfo.c_str()));
lpUrlInfo->strHostName.resize(wcslen(lpUrlInfo->strHostName.c_str()));
lpUrlInfo->strUserName.resize(wcslen(lpUrlInfo->strUserName.c_str()));
lpUrlInfo->strPassword.resize(wcslen(lpUrlInfo->strPassword.c_str()));
lpUrlInfo->strUrlPath.resize(wcslen(lpUrlInfo->strUrlPath.c_str()));
if (!lpUrlInfo->strExtraInfo.empty())
{
lpUrlInfo->strUrlPath += lpUrlInfo->strExtraInfo;
}
// 协议检查
if (!(INTERNET_SCHEME_HTTPS == lpUrlInfo->uc.nScheme || INTERNET_SCHEME_HTTP == lpUrlInfo->uc.nScheme))
{
return false;
}
return true;
}
CWinHttpResult CWinHttpClient::Get(const _tstring& strUrl, WinHttpProgressCallback cb/* = nullptr*/)
{
m_cbProgress = cb;
return _DoRequest(strUrl, _T("GET"), "");
}
CWinHttpResult CWinHttpClient::Post(const _tstring& strUrl, const std::string& strParam, WinHttpProgressCallback cb/* = nullptr*/)
{
m_cbProgress = cb;
return _DoRequest(strUrl, _T("POST"), strParam);
}
CWinHttpResult CWinHttpClient::GetEx(const _tstring& strUrl, WinHttpProgressCallback cb/* = nullptr*/, DWORD dwThreadCount/* = 1*/)
{
m_strFilePath.clear();
return _GetEx(strUrl, cb, dwThreadCount);
}
bool CWinHttpClient::DownloadFile(const _tstring& strUrl, const _tstring& strFile, WinHttpProgressCallback cb/* = nullptr*/, DWORD dwThreadCount/* = 4*/)
{
m_strFilePath = strFile;
if (m_strFilePath.empty())
{
size_t nPos = strUrl.find_last_of(_T("/"));
if (_tstring::npos != nPos)
{
m_strFilePath = strUrl.substr(nPos + 1);
}
}
// 清空文件
HANDLE hFile = INVALID_HANDLE_VALUE;
hFile = ::CreateFile(strFile.c_str(),
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE,
NULL,
TRUNCATE_EXISTING,
FILE_ATTRIBUTE_ARCHIVE,
NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
::CloseHandle(hFile);
}
CWinHttpResult httpResult = _GetEx(strUrl, cb, dwThreadCount);
return true;
}
CWinHttpResult CWinHttpClient::_DoRequest(const _tstring& strUrl, const _tstring& strMethod, const std::string& strParam)
{
WINHTTP_URL_INFO urlInfo;
CWinHttpResult httpResult;
m_strFilePath.clear();
if (!_CrackUrl(strUrl, &urlInfo))
{
_PrintError(_T("_CrackUrl"));
return httpResult;
}
do
{
// 初始化 HTTP 会话
std::wstring wstrAgent = TStrToWStr(m_strAgent);
DWORD dwAccessType = WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY;
LPCWSTR pszProxy = WINHTTP_NO_PROXY_NAME;
LPCWSTR pszProxyBypass = WINHTTP_NO_PROXY_BYPASS;
DWORD dwSessionFlags = WINHTTP_FLAG_ASYNC; //WINHTTP_FLAG_ASYNC / WINHTTP_FLAG_SECURE_DEFAULTS
if (!_WinHttpOpen(wstrAgent.c_str(), dwAccessType, pszProxy, pszProxyBypass, dwSessionFlags))
{
_PrintError(_T("_WinHttpOpen"));
break;
}
// 设置回调函数
if (!_WinHttpSetStatusCallback(WinHttpStatusCallback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS))
{
_PrintError(_T("WinHttpSetStatusCallback"));
break;
}
// 设置状态回调上下文
DWORD_PTR dwContext = (DWORD_PTR)this;
if (!_WinHttpSetSessionOption(WINHTTP_OPTION_CONTEXT_VALUE, &dwContext, sizeof(dwContext)))
{
_PrintError(_T("WinHttpSetOption"));
break;
}
// 连接 HTTP 会话
if (!_WinHttpConnect(urlInfo.strHostName.c_str(), urlInfo.uc.nPort))
{
_PrintError(_T("_WinHttpConnect"));
break;
}
// 创建 HTTP 请求句柄
LPCWSTR pwszReferrer = WINHTTP_NO_REFERER;
LPCWSTR* pwszAcceptTypes = WINHTTP_DEFAULT_ACCEPT_TYPES;
DWORD dwRequestFlags = (INTERNET_SCHEME_HTTPS == urlInfo.uc.nScheme) ? WINHTTP_FLAG_SECURE : 0;
if (!_WinHttpOpenRequest(strMethod, urlInfo.strUrlPath.c_str(), NULL, pwszReferrer, pwszAcceptTypes, dwRequestFlags))
{
_PrintError(_T("_WinHttpOpenRequest"));
break;
}
// 设置请求超时
if (!_WinHttpSetRequestTimeouts(500, 500, 1000, 1000))
{
_PrintError(_T("WinHttpSetTimeouts"));
break;
}
// 设置请求范围
if (!_SetRequestDataRange(m_ullStart, m_ullLength))
{
_PrintError(_T("_SetRequestDataRange"));
break;
}
// 发送请求
_tstring strHeader = _GetRequestHeaderString();
if (!_WinHttpSendRequest(strHeader, (LPVOID)strParam.data(), (DWORD)strParam.size(), (DWORD_PTR)this))
{
_PrintError(_T("_WinHttpSendRequest"));
break;
}
// 等待接收请求响应
if (!_WinHttpReceiveResponse(m_hRequest))
{
_PrintError(_T("_WinHttpReceiveResponse"));
break;
}
// 查询文件大小
ULONGLONG UllContentLength = 0;
if (!_QueryContentLength(&UllContentLength))
{
_PrintError(_T("_QueryContentLength"));
break;
}
// 获取响应码
httpResult.code = _GetStatusCode(m_hRequest);
if (httpResult.code < 200 || httpResult.code >= 300)
{
_PrintError(_T("_GetStatusCode"));
break;
}
// 接收请求数据
if (!_InternetReadData(httpResult.result))
{
_PrintError(_T("_InternetReadData"));
break;
}
} while (false);
// 关闭相关句柄
Close();
return httpResult;
}
CWinHttpResult CWinHttpClient::_GetEx(const _tstring& strUrl, WinHttpProgressCallback cb/* = nullptr*/, DWORD dwThreadCount/* = 6*/)
{
WINHTTP_URL_INFO urlInfo;
CWinHttpResult httpResult;
if (!_CrackUrl(strUrl, &urlInfo))
{
_PrintError(_T("_CrackUrl"));
return httpResult;
}
m_cbProgress = cb;
do
{
// 初始化 HTTP 会话
std::wstring wstrAgent = TStrToWStr(m_strAgent);
DWORD dwAccessType = WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY;
LPCWSTR pszProxy = WINHTTP_NO_PROXY_NAME;
LPCWSTR pszProxyBypass = WINHTTP_NO_PROXY_BYPASS;
DWORD dwSessionFlags = WINHTTP_FLAG_ASYNC; //WINHTTP_FLAG_ASYNC / WINHTTP_FLAG_SECURE_DEFAULTS
if (!_WinHttpOpen(wstrAgent.c_str(), dwAccessType, pszProxy, pszProxyBypass, dwSessionFlags))
{
_PrintError(_T("_WinHttpOpen"));
break;
}
// 设置回调函数
if (!_WinHttpSetStatusCallback(WinHttpStatusCallback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS))
{
_PrintError(_T("WinHttpSetStatusCallback"));
break;
}
// 设置状态回调上下文
DWORD_PTR dwContext = (DWORD_PTR)this;
if (!_WinHttpSetSessionOption(WINHTTP_OPTION_CONTEXT_VALUE, &dwContext, sizeof(dwContext)))
{
_PrintError(_T("WinHttpSetOption"));
break;
}
// 连接 HTTP 会话
if (!_WinHttpConnect(urlInfo.strHostName.c_str(), urlInfo.uc.nPort))
{
_PrintError(_T("_WinHttpConnect"));
break;
}
// 创建 HTTP 请求句柄
LPCWSTR pwszReferrer = WINHTTP_NO_REFERER;
LPCWSTR* pwszAcceptTypes = WINHTTP_DEFAULT_ACCEPT_TYPES;
DWORD dwRequestFlags = (INTERNET_SCHEME_HTTPS == urlInfo.uc.nScheme) ? WINHTTP_FLAG_SECURE : 0;
if (!_WinHttpOpenRequest(_T("GET"), urlInfo.strUrlPath.c_str(), NULL, pwszReferrer, pwszAcceptTypes, dwRequestFlags))
{
_PrintError(_T("_WinHttpOpenRequest"));
break;
}
// 设置请求超时
if (!_WinHttpSetRequestTimeouts(1000, 1000, 3000, 5000))
{
_PrintError(_T("WinHttpSetTimeouts"));
break;
}
// 发送请求
_tstring strHeader = _GetRequestHeaderString();
if (!_WinHttpSendRequest(strHeader, NULL, 0, (DWORD_PTR)this))
{
_PrintError(_T("_WinHttpSendRequest"));
break;
}
// 等待接收请求响应
if (!_WinHttpReceiveResponse(m_hRequest))
{
_PrintError(_T("_WinHttpReceiveResponse"));
break;
}
// 获取响应码
httpResult.code = _GetStatusCode(m_hRequest);
if (httpResult.code < 200 || httpResult.code >= 300)
{
_PrintError(_T("_GetStatusCode"));
break;
}
// 查询文件大小
ULONGLONG ullContentLength = 0;
if (!_QueryContentLength(&ullContentLength))
{
_PrintError(_T("QueryContentLength"));
break;
}
// 是否支持接收范围请求
bool fAcceptRange = _IsSupportAcceptRanges();
// 多线程接收请求数据
if (ullContentLength > 0 && fAcceptRange && dwThreadCount > 1)
{
httpResult = _MultiThreadRequest(strUrl, ullContentLength, dwThreadCount);
}
else
{
// 单线程接收请求数据
if (!_InternetReadData(httpResult.result))
{
_PrintError(_T("_InternetReadData"));
break;
}
}
} while (false);
// 关闭相关句柄
Close();
return httpResult;
}
CWinHttpResult CWinHttpClient::_MultiThreadRequest(const _tstring& strUrl, ULONGLONG ullContentLength, DWORD dwThreadCount/* = 1*/)
{
CWinHttpResult httpResult;
std::atomic<int> nTaskCount = dwThreadCount;
bool fQuit = false;
DWORD ullSinglePackageSize = ullContentLength / dwThreadCount;
std::vector<CWinHttpClient> vTaskClients;
vTaskClients.resize(dwThreadCount);
std::vector<CWinHttpResult> vTaskResults;
vTaskResults.resize(dwThreadCount);
std::vector<std::thread> vTaskThreads;
for (DWORD i = 0; i < dwThreadCount; i++)
{
ULONGLONG ullBeginPos = ullSinglePackageSize * i;
ULONGLONG ullPackageSize = (i != dwThreadCount - 1) ? ullSinglePackageSize : ullContentLength - ullBeginPos;
vTaskClients[i].SetReQuestDataRange(ullBeginPos, ullPackageSize);
vTaskClients[i].m_strAgent = m_strAgent;
vTaskClients[i].m_RequestHeader = m_RequestHeader;
vTaskClients[i].m_strFilePath = m_strFilePath;
vTaskClients[i].m_fPrint = m_fPrint;
vTaskThreads.emplace_back(std::thread([&vTaskClients, &vTaskResults, i, strUrl, &nTaskCount, &fQuit]() {
while (!fQuit)
{
vTaskClients[i].m_AsyncData.AsyncResult.dwResult = 0;
vTaskClients[i].m_AsyncData.AsyncResult.dwError = 0;
// 修改请求起始位置
vTaskClients[i].m_ullStart += vTaskClients[i].m_ullDownload;
vTaskResults[i] = vTaskClients[i].Get(strUrl, [](const WINHTTP_PROGRESS_INFO& progress) {
return true;
}
);
if (vTaskResults[i].code >= 200 && vTaskResults[i].code < 300)
{
break;
}
}
nTaskCount--;
}
)
);
}
// 进度统计用变量
clock_t refreshInterval = 1000;
clock_t curTime = ::clock();
clock_t startTime = curTime;
clock_t lastTime = curTime;
double lfRemainTime = 0.0f;
double lfSpeed = 0.0f;
ULONGLONG ullLastDownload = 0;
ULONGLONG ullTotalLength = ullContentLength;
while (!fQuit)
{
ULONGLONG ullDownloaded = 0;
// 进度计算
if (m_cbProgress)
{
// 速度计算
clock_t curTime = ::clock();
if (curTime - lastTime >= refreshInterval || 0 == nTaskCount)
{
for (auto& item : vTaskClients)
{
ullDownloaded += item.m_ullDownload;
}
lfSpeed = (double)(ullDownloaded - ullLastDownload) / ((double)refreshInterval / 1000.0f);
if (isinf(lfSpeed) || isnan(lfSpeed))
{
lfSpeed = 0.0f;
}
// 进度报告
{
WINHTTP_PROGRESS_INFO progress = { 0 };
progress.lfProgress = (double)ullDownloaded / (double)ullTotalLength;
progress.ullCur = ullDownloaded;
progress.ullTotal = ullTotalLength;
progress.lfSpeed = lfSpeed;
progress.costTime = curTime - startTime;
progress.lfRemainTime = ((double)(ullTotalLength - ullDownloaded)) / lfSpeed;
progress.nActiveThread = nTaskCount;
progress.nTotalThread = dwThreadCount;
if (!m_cbProgress(progress))
{
fQuit = true;
break;
}
}
lastTime = curTime;
ullLastDownload = ullDownloaded;
}
}
if (0 == nTaskCount)
{
break;
}
::Sleep(10);
}
// 主动退出
if (fQuit)
{
for (auto& item : vTaskClients)
{
item.Close();
}
}
// 等待线程结束
for (auto& item : vTaskThreads)
{
if (item.joinable())
{
item.join();
}
}
// 结果拼接
if(m_strFilePath.empty())
{
for (auto& item : vTaskResults)
{
httpResult.result += item.result;
if (item.code >= 200 && item.code < 300)
{
httpResult.code = item.code;
}
else
{
httpResult.code = 0;
}
}
}
return httpResult;
}
std::string CWinHttpClient::_WStrToMultiStr(UINT CodePage, const std::wstring& str)
{
int cbMultiByte = ::WideCharToMultiByte(CodePage, 0, str.c_str(), -1, NULL, 0, NULL, 0);
std::string strResult(cbMultiByte, 0);
size_t nConverted = ::WideCharToMultiByte(CodePage, 0, str.c_str(), (int)str.size(), &strResult[0], (int)strResult.size(), NULL, NULL);
strResult.resize(nConverted);
return strResult;
}
std::wstring CWinHttpClient::_MultiStrToWStr(UINT CodePage, const std::string& str)
{
int cchWideChar = ::MultiByteToWideChar(CodePage, 0, str.c_str(), -1, NULL, NULL);
std::wstring strResult(cchWideChar, 0);
size_t nConverted = ::MultiByteToWideChar(CodePage, 0, str.c_str(), (int)str.size(), &strResult[0], (int)strResult.size());
strResult.resize(nConverted);
return strResult;
}
std::string CWinHttpClient::TStrToU8Str(const _tstring& str)
{
#ifdef _UNICODE
return WStrToMultiStr(CP_UTF8, str);
#else
return _WStrToMultiStr(CP_UTF8, _MultiStrToWStr(CP_ACP, str));
#endif
}
std::wstring CWinHttpClient::TStrToWStr(const _tstring& str)
{
#ifdef _UNICODE
return str;
#else
return _MultiStrToWStr(CP_ACP, str);
#endif
}
_tstring CWinHttpClient::WStrToTStr(const std::wstring& str)
{
#ifdef _UNICODE
return str;
#else
return _WStrToMultiStr(CP_ACP, str);
#endif
}
void CWinHttpClient::AddRequestHeader(const _tstring strCaption, const _tstring strData)
{
auto itFind = m_RequestHeader.find(strCaption);
if (m_RequestHeader.end() == itFind)
{
std::set<_tstring> setData;
setData.insert(strData);
m_RequestHeader.insert(std::make_pair(strCaption, setData));
}
else
{
itFind->second.insert(strData);
}
}
void CWinHttpClient::RemoveRequestHeader(const _tstring strCaption, const _tstring strData)
{
auto itFind = m_RequestHeader.find(strCaption);
if (m_RequestHeader.end() == itFind)
{
return;
}
if (strData.empty())
{
m_RequestHeader.erase(itFind);
}
else
{
itFind->second.erase(strData);
if (itFind->second.empty())
{
m_RequestHeader.erase(itFind);
}
}
}
void CWinHttpClient::ClearRequestHeader()
{
m_RequestHeader.clear();
}
void CWinHttpClient::SetAgent(const _tstring strAgent)
{
m_strAgent = strAgent;
}
void CWinHttpClient::SetReQuestDataRange(ULONGLONG ullStart, ULONGLONG ullLength)
{
m_ullStart = ullStart;
m_ullLength = ullLength;
}
void CWinHttpClient::SetPrintStatus(bool fEnable/* = true*/)
{
m_fPrint = fEnable;
}
_tstring CWinHttpClient::_GetRequestHeaderString()
{
_tstring strResult;
size_t nItemIndex = 0;
size_t nItemCount = m_RequestHeader.size();
for (const auto& item : m_RequestHeader)
{
strResult += item.first;
strResult += _T(": ");
size_t nDataIndex = 0;
size_t nDataCount = item.second.size();
for (const auto& data : item.second)
{
strResult += data;
nDataIndex++;
if (nDataIndex < nDataCount)
{
strResult += _T(";");
}
}
nItemIndex++;
if (nItemIndex < nItemCount)
{
strResult += _T("\r\n");
}
}
return strResult;
}
bool CWinHttpClient::_QueryContentLength(PULONGLONG lpUllContentLength)
{
DWORD dwInfoLevel = WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER64;
ULONGLONG ullContentLength = 0;
DWORD dwBufferLength = sizeof(ullContentLength);
DWORD dwIndex = 0;
if (!_WinHttpQueryHeaders(dwInfoLevel, WINHTTP_HEADER_NAME_BY_INDEX, &ullContentLength, &dwBufferLength, &dwIndex))
{
// 无法找到请求的标头
if (ERROR_WINHTTP_HEADER_NOT_FOUND == ::GetLastError())
{
*lpUllContentLength = -1;
return true;
}
_PrintError(_T("WinHttpQueryHeaders"));
*lpUllContentLength = 0;
return false;
}
*lpUllContentLength = ullContentLength;
return true;
}
bool CWinHttpClient::_IsSupportAcceptRanges()
{
WCHAR szBuf[MAX_PATH] = { 0 };
DWORD dwBufferLength = sizeof(szBuf);
DWORD dwIndex = 0;
if (!_WinHttpQueryHeaders(WINHTTP_QUERY_ACCEPT_RANGES, WINHTTP_HEADER_NAME_BY_INDEX, &szBuf, &dwBufferLength, &dwIndex))
{
return false;
}
if (0 == _wcsicmp(szBuf, L"bytes"))
{
return true;
}
return false;
}
bool CWinHttpClient::_SetRequestDataRange(LONGLONG nBegin, LONGLONG nLength)
{
WCHAR szBuf[MAX_PATH] = { 0 };
if (0 == nLength)
{
(void)::StringCchPrintfW(szBuf, _countof(szBuf), L"Range:bytes=%lld-%lld", nBegin, nBegin);
}
else if (nLength > 0)
{
(void)::StringCchPrintfW(szBuf, _countof(szBuf), L"Range:bytes=%lld-%lld", nBegin, nBegin + nLength - 1);
}
else
{
(void)::StringCchPrintfW(szBuf, _countof(szBuf), L"Range:bytes=%lld-", nBegin);
}
return ::WinHttpAddRequestHeaders(m_hRequest, szBuf, (DWORD)wcslen(szBuf), 0);
}
DWORD CWinHttpClient::_GetStatusCode(HINTERNET hRequest)
{
DWORD dwInfoLevel = WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER;
DWORD dwRespCode = 0;
DWORD dwBufferLength = sizeof(dwRespCode);
DWORD dwIndex = 0;
if (!_WinHttpQueryHeaders(dwInfoLevel, WINHTTP_HEADER_NAME_BY_INDEX, &dwRespCode, &dwBufferLength, &dwIndex))
{
return dwRespCode;
}
return dwRespCode;
}
bool CWinHttpClient::_InternetReadData(std::string& strData)
{
HANDLE hFile = INVALID_HANDLE_VALUE;
bool fResult = false;
// 查询文件大小
ULONGLONG ullContentLength = 0;
if (!_QueryContentLength(&ullContentLength))
{
_PrintError(_T("QueryContentLength"));
return false;
}
// 分配数据缓冲
try
{
if (-1 != ullContentLength)
{
strData.resize(ullContentLength, 0);
}
}
catch (...)
{
_PrintError(_T("std::string resize"));
return false;
}
// 接收数据
LPBYTE lpBufPos = (LPBYTE)strData.data();
std::string strBuf;
ULONGLONG ullDownloaded = 0;
ULONGLONG ullTotalLength = ullContentLength;
ULONGLONG ullLastDownload = 0;
// 进度统计用变量
clock_t refreshInterval = 1000;
clock_t curTime = ::clock();
clock_t startTime = curTime;
clock_t lastTime = curTime;
double lfRemainTime = 0.0f;
double lfSpeed = 0.0f;
if (!m_strFilePath.empty())
{
LARGE_INTEGER liDistanceToMove = { 0 };
// 共享读写 创建/打开 文件, 多线程读写
hFile = ::CreateFile(m_strFilePath.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"));
return false;
}
// 设置数据写入位置
liDistanceToMove.QuadPart = m_ullStart;
::SetFilePointerEx(hFile, liDistanceToMove, NULL, FILE_BEGIN);
}
do
{
// 检查可用数据
DWORD dwNumberOfBytesAvailable = 0;
DWORD dwNumberOfBytesRead = 0;
if (!_WinHttpQueryDataAvailable(&dwNumberOfBytesAvailable))
{
_PrintError(_T("_WinHttpQueryDataAvailable"));
break;
}
if (dwNumberOfBytesAvailable > 0)
{
try
{
strBuf.resize(dwNumberOfBytesAvailable);
}
catch (...)
{
_PrintError(_T("std::vector resize"));
break;
}
if (!_WinHttpReadData(&strBuf[0], dwNumberOfBytesAvailable, &dwNumberOfBytesRead))
{
_PrintError(_T("_WinHttpReadData"));
break;
}
// 写入到缓冲
if (-1 == ullContentLength)
{
strData += strBuf;
ullDownloaded += strBuf.size();
}
else
{
memcpy(lpBufPos, &strBuf[0], dwNumberOfBytesRead);
lpBufPos += dwNumberOfBytesRead;
ullDownloaded += dwNumberOfBytesRead;
}
if (INVALID_HANDLE_VALUE != hFile)
{
DWORD dwWritten = 0;
if (!::WriteFile(hFile, strBuf.data(), dwNumberOfBytesRead, &dwWritten, NULL))
{
_PrintError(_T("WriteFile"));
break;
}
}
m_ullDownload += dwNumberOfBytesRead;
}
// 进度计算
if (m_cbProgress)
{
// 速度计算
clock_t curTime = ::clock();
if (curTime - lastTime >= refreshInterval || 0 == dwNumberOfBytesAvailable)
{
lfSpeed = (double)(ullDownloaded - ullLastDownload) / ((double)refreshInterval / 1000.0f);
if (isinf(lfSpeed) || isnan(lfSpeed))
{
lfSpeed = 0.0f;
}
if (0 == dwNumberOfBytesAvailable)
{
ullTotalLength = ullDownloaded;
}
// 进度报告
{
WINHTTP_PROGRESS_INFO progress = { 0 };
progress.lfProgress = (double)ullDownloaded / (double)ullTotalLength;
progress.ullCur = ullDownloaded;
progress.ullTotal = ullTotalLength;
progress.lfSpeed = lfSpeed;
progress.costTime = curTime - startTime;
progress.lfRemainTime = ((double)(ullTotalLength - ullDownloaded)) / lfSpeed;
progress.nActiveThread = 1;
progress.nTotalThread = 1;
if (!m_cbProgress(progress))
{
break;
}
}
lastTime = curTime;
ullLastDownload = ullDownloaded;
}
}
// 检查读取是否结束
if (!dwNumberOfBytesAvailable)
{
fResult = true;
break;
}
} while (true);
if (INVALID_HANDLE_VALUE != hFile)
{
::CloseHandle(hFile);
}
return fResult;
}
void CWinHttpClient::_PrintError(LPCTSTR lpszError) const
{
return;
ConsoleOutput(_T("[Error][LastError: %d Error: %d Result: %d][%s]\r\n"),
::GetLastError(),
m_AsyncData.AsyncResult.dwError,
m_AsyncData.AsyncResult.dwResult,
lpszError
);
}
void CWinHttpClient::ConsoleOutput(LPCTSTR pFormat, ...)
{
size_t nCchCount = MAX_PATH;
_tstring strResult(nCchCount, 0);
va_list args;
va_start(args, pFormat);
do
{
//格式化输出字符串
int nSize = _vsntprintf_s(&strResult[0], nCchCount, _TRUNCATE, pFormat, args);
if (-1 != nSize)
{
HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
::WriteConsole(console, strResult.c_str(), nSize, NULL, NULL);
break;
}
//缓冲大小超限终止
if (nCchCount >= INT32_MAX)
{
break;
}
//重新分配缓冲
nCchCount *= 2;
strResult.resize(nCchCount);
} while (true);
va_end(args);
}
#define WINHTTP_STATUS_TEXT(_code) std::make_pair(_code, _T(#_code)),
void CWinHttpClient::_PrintStatus(DWORD dwCode, LPVOID lpvStatusInfo, DWORD dwStatusInfoLength)
{
std::map<DWORD, _tstring> mapWinHttpCode = {
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_RESOLVING_NAME )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_NAME_RESOLVED )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SENDING_REQUEST )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_REQUEST_SENT )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_HANDLE_CREATED )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_DETECTING_PROXY )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_REDIRECT )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SECURE_FAILURE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_READ_COMPLETE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_REQUEST_ERROR )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_GETPROXYSETTINGS_COMPLETE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SETTINGS_WRITE_COMPLETE )
WINHTTP_STATUS_TEXT(WINHTTP_CALLBACK_STATUS_SETTINGS_READ_COMPLETE )
};
auto itFind = mapWinHttpCode.find(dwCode);
if (mapWinHttpCode.end() != itFind)
{
ConsoleOutput(_T("%08X %-50s "), itFind->first, itFind->second.c_str());
}
if (dwCode == WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER ||
dwCode == WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER ||
dwCode == WINHTTP_CALLBACK_STATUS_NAME_RESOLVED ||
dwCode == WINHTTP_CALLBACK_STATUS_REDIRECT
)
{
if (NULL != lpvStatusInfo)
{
LPWSTR lpData = (LPWSTR)lpvStatusInfo;
ConsoleOutput(_T("Data %s"), WStrToTStr(lpData).c_str());
}
}
if (dwCode == WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE ||
dwCode == WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE ||
dwCode == WINHTTP_CALLBACK_STATUS_REQUEST_SENT ||
dwCode == WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED ||
dwCode == WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE ||
dwCode == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE
)
{
if (NULL != lpvStatusInfo)
{
LPDWORD lpData = (LPDWORD)lpvStatusInfo;
ConsoleOutput(_T("Data %d(%08X)"), *lpData, *lpData);
}
}
if (dwCode == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR
)
{
if (NULL != lpvStatusInfo)
{
LPWINHTTP_ASYNC_RESULT lpData = (LPWINHTTP_ASYNC_RESULT)lpvStatusInfo;
ConsoleOutput(_T("Error %d(%08X) Result: %d(%08X)"),
lpData->dwError, lpData->dwError,
lpData->dwResult, lpData->dwResult
);
}
}
ConsoleOutput(_T("\r\n"));
}
VOID CALLBACK CWinHttpClient::WinHttpStatusCallback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwStatus, LPVOID lpvInfo, DWORD dwInfoLength)
{
CWinHttpClient* pThis = (CWinHttpClient*)dwContext;
if (nullptr != pThis)
{
pThis->_WinHttpStatusCallback(hInternet, dwStatus, lpvInfo, dwInfoLength);
}
}
// 状态回调
void CWinHttpClient::_WinHttpStatusCallback(HINTERNET hInternet, DWORD dwStatus, LPVOID lpvInfo, DWORD dwInfoLength)
{
if (m_fPrint)
{
_PrintStatus(dwStatus, lpvInfo, dwInfoLength);
}
switch (dwStatus)
{
// 关闭与服务器的连接。 lpvStatusInformation 参数为 NULL。
case WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION:
{
break;
}
// 已成功连接到服务器。 lpvStatusInformation 参数包含指向 LPWSTR 的指针,该指针以点表示法指示服务器的 IP 地址。
case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
{
LPWSTR lpData = (LPWSTR)lpvInfo;
break;
}
// 连接到服务器。 lpvStatusInformation 参数包含指向 LPWSTR 的指针,该指针以点表示法指示服务器的 IP 地址。
case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER:
{
LPWSTR lpData = (LPWSTR)lpvInfo;
break;
}
// 已成功关闭与服务器的连接。 lpvStatusInformation 参数为 NULL。
case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED:
{
break;
}
// 可以使用 WinHttpReadData 检索数据。 lpvStatusInformation 参数指向包含可用数据字节数的 DWORD。
// dwStatusInformationLength 参数本身是 4 (DWORD) 的大小。
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:// WinHttpQueryDataAvailable 完成
{
DWORD lpSize = *(LPDWORD)lpvInfo;
m_AsyncData.dwSize = lpSize;
m_AsyncData.AsyncResult.dwResult = 0;
::SetEvent(m_AsyncData.hEvent);
break;
}
// 已创建 HINTERNET 句柄。 lpvStatusInformation 参数包含指向 HINTERNET 句柄的指针。
case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED:
{
LPHINTERNET pHandle = (LPHINTERNET)lpvInfo;
m_AsyncData.dwContext = (DWORD_PTR)*pHandle;
::SetEvent(m_AsyncData.hEvent);
break;
}
// 此句柄值已终止。 lpvStatusInformation 参数包含指向 HINTERNET 句柄的指针。 此句柄不再有回调。
case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
{
LPHINTERNET pHandle = (LPHINTERNET)lpvInfo;
break;
}
// 响应标头已收到,可用于 WinHttpQueryHeaders。 lpvStatusInformation 参数为 NULL。
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: // WinHttpReceiveResponse 完成
{
m_AsyncData.AsyncResult.dwResult = 0;
::SetEvent(m_AsyncData.hEvent);
break;
}
// 从服务器收到中间 (100 级别) 状态代码消息。 lpvStatusInformation 参数包含指向指示状态代码的 DWORD 的指针。
case WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE:
{
LPDWORD pStatusCode = (LPDWORD)lpvInfo;
break;
}
// 已成功找到服务器的 IP 地址。 lpvStatusInformation 参数包含指向 LPWSTR 的指针,该指针指示已解析的名称。
case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED:
{
LPWSTR lpResolved = (LPWSTR)lpvInfo;
break;
}
// 已成功从服务器读取数据。
// lpvStatusInformation 参数包含指向调用 WinHttpReadData 中指定的缓冲区的指针。
// dwStatusInformationLength 参数包含读取的字节数。
// WinHttpWebSocketReceive 使用时,lpvStatusInformation 参数包含指向WINHTTP_WEB_SOCKET_STATUS结构的指针,
// dwStatusInformationLength 参数指示 lpvStatusInformation 的大小。
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: // WinHttpReadData 完成
{
LPBYTE* ppBuf = (LPBYTE*)lpvInfo;
DWORD dwRead = dwInfoLength;
m_AsyncData.AsyncResult.dwResult = 0;
m_AsyncData.dwSize = dwRead;
::SetEvent(m_AsyncData.hEvent);
break;
}
// 等待服务器响应请求。 lpvStatusInformation 参数为 NULL。
case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE:
{
break;
}
// HTTP 请求将自动重定向请求。 lpvStatusInformation 参数包含指向指示新 URL 的 LPWSTR 的指针。
// 此时,应用程序可以使用重定向响应读取服务器返回的任何数据,并且可以查询响应标头。 它还可以通过关闭句柄来取消操作。
case WINHTTP_CALLBACK_STATUS_REDIRECT:
{
LPWSTR lpData = (LPWSTR)lpvInfo;
break;
}
// 发送 HTTP 请求时出错。 lpvStatusInformation 参数包含指向WINHTTP_ASYNC_RESULT结构的指针。
// 其 dwResult 成员指示被调用函数的 ID,dwError 指示返回值。
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
{
DWORD dwError = ERROR_WINHTTP_INCORRECT_HANDLE_STATE;
LPWINHTTP_ASYNC_RESULT pAsyncResult = (LPWINHTTP_ASYNC_RESULT)lpvInfo;
m_AsyncData.AsyncResult = *pAsyncResult;
::SetEvent(m_AsyncData.hEvent);
break;
}
// 已成功将信息请求发送到服务器。 lpvStatusInformation 参数包含指向 DWORD 的指针,该指针指示发送的字节数。
case WINHTTP_CALLBACK_STATUS_REQUEST_SENT:
{
LPDWORD pSentBytes = (LPDWORD)lpvInfo;
break;
}
// 查找服务器名称的 IP 地址。 lpvStatusInformation 参数包含指向要解析的服务器名称的指针。
case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME:
{
LPWSTR lpName = (LPWSTR)lpvInfo;
break;
}
// 已成功从服务器收到响应。 lpvStatusInformation 参数包含指向指示接收字节数的 DWORD 的指针。
case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED:
{
LPDWORD pRecv = (LPDWORD)lpvInfo;
break;
}
// 在与服务器建立安全 (HTTPS) 连接时遇到一个或多个错误。
// lpvStatusInformation 参数包含指向 DWORD 的指针,该指针是错误值的按位 OR 组合。
// 有关详细信息,请参阅 lpvStatusInformation 的说明。
case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
{
DWORD dwCode = *(LPDWORD)lpvInfo;
switch (dwCode)
{
// 证书吊销检查已启用,但吊销检查未能验证证书是否已吊销。 用于检查吊销的服务器可能无法访问
case WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED:
{
break;
}
// SSL 证书无效
case WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT:
{
break;
}
// SSL 证书已吊销
case WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED:
{
break;
}
// 函数不熟悉生成服务器证书的证书颁发机构
case WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA:
{
break;
}
// SSL 证书公用名 (主机名字段) 不正确,例如,如果输入 www.microsoft.com 且证书上的公用名显示 www.msn.com
case WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID:
{
break;
}
// 从服务器收到的 SSL 证书日期不正确。 证书已过期。
case WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID:
{
break;
}
// 应用程序在加载 SSL 库时遇到内部错误
case WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR:
{
break;
}
}
break;
}
// 将信息请求发送到服务器。 lpvStatusInformation 参数为 NULL。
case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
{
break;
}
// 请求已成功完成。
// lpvStatusInformation 参数是传递给 WinHttpSendRequest (初始请求正文) 的 lpOptional 值,
// dwStatusInformationLength 参数指示 (传递到 WinHttpSendRequest) 传递到 winHttpSendRequest 的 dwOptionalLength 值成功写入此类初始正文字节的数目。
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: // WinHttpSendRequest 完成
{
m_AsyncData.AsyncResult.dwResult = 0;
::SetEvent(m_AsyncData.hEvent);
break;
}
// 数据已成功写入服务器。 lpvStatusInformation 参数包含指向 DWORD 的指针,该指针指示写入的字节数。
// 当 WinHttpWebSocketSend 使用时, lpvStatusInformation 参数包含指向WINHTTP_WEB_SOCKET_STATUS结构的指针,
// dwStatusInformationLength 参数指示 lpvStatusInformation 的大小。
case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: // WinHttpWriteData 完成
{
m_AsyncData.AsyncResult.dwResult = 0;
::SetEvent(m_AsyncData.hEvent);
break;
}
// 通过调用 WinHttpGetProxyForUrlEx 启动的操作已完成。 可以使用 WinHttpReadData 检索数据。
case WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE:
{
break;
}
// 通过调用 WinHttpWebSocketClose 成功关闭了连接。 lpvStatusInformation 参数为 NULL。
case WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE:
{
break;
}
// 通过调用 WinHttpWebSocketShutdown 成功关闭连接。 lpvStatusInformation 参数为 NULL。
case WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE:
{
break;
}
}
}
bool CWinHttpClient::_WinHttpOpen(LPCWSTR pszAgentW, DWORD dwAccessType, LPCWSTR pszProxyW, LPCWSTR pszProxyBypassW, DWORD dwFlags)
{
// 初始化 WinHTTP 函数的使用并返回 WinHTTP 会话句柄。
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpopen
m_hSession = ::WinHttpOpen(
pszAgentW, // 指向字符串变量的指针,此名称用作 HTTP 协议中的 用户代理
dwAccessType, // 所需的访问类型
pszProxyW, // 代理访问时要使用的代理服务器的名称
pszProxyBypassW, // 代理访问时要使用的代理服务器的密码
dwFlags // 指示影响此函数行为的各种选项的标志
);
return NULL != m_hSession;
}
bool CWinHttpClient::_WinHttpConnect(LPCWSTR pswzServerName, INTERNET_PORT nServerPort)
{
// 指定 HTTP 请求的初始目标服务器,并将 HINTERNET 连接句柄返回到该初始目标的 HTTP 会话
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpconnect
// 即使 WinHTTP 在异步模式下使用(即在 WinHttpOpen中设置 WINHTTP_FLAG_ASYNC),此函数也会同步运行
m_hConnect = ::WinHttpConnect(
m_hSession, // 由先前调用 WinHttpOpen 返回的有效 HINTERNETWinHTTP 会话句柄
pswzServerName, // HTTP 服务器的主机名
nServerPort, // 建立连接的服务器上的 TCP/IP 端口
0 // 保留参数, 必须为0
);
return NULL != m_hConnect;
}
bool CWinHttpClient::_WinHttpOpenRequest(const _tstring& strVerb, LPCWSTR pwszObjectName, LPCWSTR pwszVersion, LPCWSTR pwszReferrer, LPCWSTR *ppwszAcceptTypes, DWORD dwFlags)
{
// 创建 HTTP 请求句柄
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpopenrequest
HINTERNET hRequest = ::WinHttpOpenRequest(
m_hConnect, // WinHttpConnect 返回的 HTTP 会话的 HINTERNET 连接句柄
TStrToWStr(strVerb).c_str(), // 请求的 HTTP 谓词
pwszObjectName, // 指定 HTTP 谓词的目标资源名称
pwszVersion, // HTTP 版本的字符串的指针
pwszReferrer, // 指定从中获取 请求 pwszObjectName 中的 URL 的文档的 URL
ppwszAcceptTypes, // 指定客户端接受的媒体类型
dwFlags // Internet 标志值
);
// 等待异步请求完成
if (!_WaitForAsyncEvent())
{
return NULL;
}
m_hRequest = (HINTERNET)m_AsyncData.dwContext;
return NULL != m_hRequest;
}
bool CWinHttpClient::_WinHttpSetSessionTimeouts(int nResolveTimeout, int nConnectTimeout,int nSendTimeout,int nReceiveTimeout)
{
// 设置与 HTTP 事务相关的超时。
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsettimeouts
// 即使在异步模式下使用 WinHTTP (即在 WinHttpOpen) 中设置了WINHTTP_FLAG_ASYNC时,此函数也会同步运行。
return ::WinHttpSetTimeouts(
m_hSession, // WinHttpOpen 或 WinHttpOpenRequest 返回的 HINTERNET 句柄。
nResolveTimeout, // 名称解析的超时值(以毫秒为单位)
nConnectTimeout, // 服务器连接请求的超时值(以毫秒为单位)
nSendTimeout, // 发送请求的超时值(以毫秒为单位)
nReceiveTimeout // 接收对请求的响应超超时值(以毫秒为单位)
);
}
bool CWinHttpClient::_WinHttpSetRequestTimeouts(int nResolveTimeout, int nConnectTimeout,int nSendTimeout,int nReceiveTimeout)
{
// 设置与 HTTP 事务相关的超时。
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsettimeouts
// 即使在异步模式下使用 WinHTTP (即在 WinHttpOpen) 中设置了WINHTTP_FLAG_ASYNC时,此函数也会同步运行。
return ::WinHttpSetTimeouts(
m_hRequest, // WinHttpOpen 或 WinHttpOpenRequest 返回的 HINTERNET 句柄。
nResolveTimeout, // 名称解析的超时值(以毫秒为单位)
nConnectTimeout, // 服务器连接请求的超时值(以毫秒为单位)
nSendTimeout, // 发送请求的超时值(以毫秒为单位)
nReceiveTimeout // 接收对请求的响应超超时值(以毫秒为单位)
);
}
bool CWinHttpClient::_WinHttpSetStatusCallback(WINHTTP_STATUS_CALLBACK lpfnInternetCallback, DWORD dwNotificationFlags
)
{
// 设置回调函数
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsetstatuscallback
WINHTTP_STATUS_CALLBACK statusCallback = ::WinHttpSetStatusCallback(
m_hSession, // 要为其设置回调的 HINTERNET 句柄
lpfnInternetCallback, // 指向进度时要调用的回调函数的指针
dwNotificationFlags, // 无符号长整数值,该值指定标志以指示哪些事件激活回调函数
0
);
if (WINHTTP_INVALID_STATUS_CALLBACK == statusCallback)
{
return false;
}
return true;
}
bool CWinHttpClient::_WaitForAsyncEvent(DWORD dwMilliseconds/* = INFINITE*/)
{
// 等待异步事件响应
m_AsyncData.dwWait = ::WaitForSingleObject(m_AsyncData.hEvent, dwMilliseconds);
if (m_fAbort)
{
return false;
}
if (WAIT_OBJECT_0 == m_AsyncData.dwWait && 0 == m_AsyncData.AsyncResult.dwResult)
{
return true;
}
return false;
}
bool CWinHttpClient::_WinHttpSetSessionOption(DWORD dwOption, LPVOID lpBuffer, DWORD dwBufferLength)
{
// 设置 Internet 选项。
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsetoption
return ::WinHttpSetOption(m_hSession, dwOption, lpBuffer, dwBufferLength);
}
bool CWinHttpClient::_WinHttpSetRequestOption(DWORD dwOption, LPVOID lpBuffer, DWORD dwBufferLength)
{
// 设置 Internet 选项。
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsetoption
return ::WinHttpSetOption(m_hRequest, dwOption, lpBuffer, dwBufferLength);
}
bool CWinHttpClient::_WinHttpSendRequest(_tstring strHeader, LPVOID lpData, DWORD dwSize, DWORD_PTR dwContext)
{
std::wstring wstrHeader = TStrToWStr(strHeader);
LPCWSTR lpHeader = (LPCWSTR)wstrHeader.data();
DWORD dwHeaderSize = (DWORD)wstrHeader.size();
if (wstrHeader.empty())
{
lpHeader = WINHTTP_NO_ADDITIONAL_HEADERS;
dwHeaderSize = 0;
}
bool fResult = false;
do
{
// 发送请求
// 将指定的请求发送到 HTTP 服务器。
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsendrequest
if (::WinHttpSendRequest(
m_hRequest, //WinHttpOpenRequest 返回的 HINTERNET 句柄
lpHeader, //要追加到请求的其他标头
dwHeaderSize, //附加标头的长度(以字符为单位)
lpData, //请求标头之后发送的任何可选数据
dwSize, //可选数据的长度(以字节为单位)
dwSize, //发送的总数据的长度
dwContext //上下文
))
{
fResult = true;
break;
}
// 安全 HTTP 服务器需要客户端证书
if (ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED != ::GetLastError())
{
break;
}
// 设置 Internet 选项
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsetoption
if (!_WinHttpSetRequestOption(
WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
WINHTTP_NO_CLIENT_CERT_CONTEXT,
0
))
{
break;
}
// 再次发送请求
// 将指定的请求发送到 HTTP 服务器。
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpsendrequest
if (!::WinHttpSendRequest(
m_hRequest, // WinHttpOpenRequest 返回的 HINTERNET 句柄
lpHeader, // 要追加到请求的其他标头
dwHeaderSize, // 附加标头的长度(以字符为单位)
lpData, // 请求标头之后发送的任何可选数据
dwSize, // 可选数据的长度(以字节为单位)
dwSize, // 发送的总数据的长度
NULL
))
{
break;
}
fResult = true;
} while (false);
if (!fResult)
{
return false;
}
// 等待异步请求完成
return _WaitForAsyncEvent();
}
bool CWinHttpClient::_WinHttpReceiveResponse(HINTERNET hRequest)
{
// 等待接收 WinHttpSendRequest 发起的 HTTP 请求的响应。
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpreceiveresponse
if (!::WinHttpReceiveResponse(
hRequest, // WINHttpOpenRequest 返回并由 WinHttpSendRequest 发送的 HINTERNET 句柄。
NULL // 此参数是保留的,必须为 NULL。
))
{
return false;
}
// 等待异步请求完成
return _WaitForAsyncEvent();
}
bool CWinHttpClient::_WinHttpQueryHeaders(DWORD dwInfoLevel, LPCWSTR pwszName, LPVOID lpBuffer, LPDWORD lpdwBufferLength, LPDWORD lpdwIndex)
{
return ::WinHttpQueryHeaders(
m_hRequest, //WinHttpOpenRequest 返回的 HINTERNET 请求句柄
dwInfoLevel, //指定“查询信息标志”页上列出的属性标志和修饰符标志的组合
pwszName, //标头名称
lpBuffer, //接收信息的缓冲区
lpdwBufferLength, //数据缓冲区的长度
lpdwIndex //从零开始的标头索引的指针,用于枚举具有相同名称的多个标头
);
}
bool CWinHttpClient::_WinHttpQueryDataAvailable(LPDWORD lpdwNumberOfBytesAvailable)
{
m_AsyncData.dwSize = 0;
// 返回可使用 WinHttpReadData 读取的数据量(以字节为单位)。
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpquerydataavailable
if (!::WinHttpQueryDataAvailable(
m_hRequest, // WinHttpOpenRequest 返回的有效 HINTERNET 句柄。
// WinHttpReceiveResponse 必须已为此句柄调用,并在调用 WinHttpQueryDataAvailable 之前完成。
NULL // 指向接收可用字节数的无符号长整数变量的指针。
// 在异步模式下使用 WinHTTP 时,始终将此参数设置为 NULL ,并在回调函数中检索数据;
// 不这样做可能会导致内存故障。
))
{
return false;
}
// 等待异步请求完成
if (!_WaitForAsyncEvent())
{
return false;
}
*lpdwNumberOfBytesAvailable = m_AsyncData.dwSize;
return true;
}
bool CWinHttpClient::_WinHttpReadData(LPVOID lpBuffer, DWORD dwNumberOfBytesToRead, LPDWORD lpdwNumberOfBytesRead)
{
m_AsyncData.dwSize = 0;
// 从 WinHttpOpenRequest 函数打开的句柄读取数据。
// https://learn.microsoft.com/zh-cn/windows/win32/api/winhttp/nf-winhttp-winhttpreaddata
if (!::WinHttpReadData(
m_hRequest, // 从上一次调用 WinHttpOpenRequest 返回的有效 HINTERNET 句柄。
lpBuffer, // 指向接收读取数据的缓冲区的指针。 确保此缓冲区在 WinHttpReadData 完成之前保持有效。
dwNumberOfBytesToRead, // 要读取的字节数的无符号长整数值。
NULL // 指向接收读取字节数的无符号长整数变量的指针。
// WinHttpReadData 在执行任何工作或错误检查之前将此值设置为零。
// 异步使用 WinHTTP 时,始终将此参数设置为 NULL ,并在回调函数中检索信息;
// 不这样做可能会导致内存故障。
))
{
return false;
}
// 等待异步请求完成
if (!_WaitForAsyncEvent())
{
return false;
}
*lpdwNumberOfBytesRead = m_AsyncData.dwSize;
return true;
}
main.cpp
#include <iostream>
#include "CWinHttpClient.h"
#define UPDATE_URL1 _T("https://gitee.com/flame_cyclone/fc_font_tool/raw/master/Release/update.json")
#define UPDATE_URL2 _T("https://gitee.com/flame_cyclone/fc_font_tool/releases/download/1.0.0.4/FC_Font_Tool.exe")
#define TEST_NVIDIA_DRIVER _T("https://cn.download.nvidia.com/Windows/561.09/561.09-desktop-win10-win11-64bit-international-dch-whql.exe")
#define TEST_AMD_DRIVER _T("https://drivers.amd.com/drivers/whql-amd-software-adrenalin-edition-24.8.1-win10-win11-aug-rdna.exe")
#define TEST_BILIBILI_DM _T("https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory?roomid=4412054")
#define TEST_WEPE_URL _T("https://mirrors.lzu.edu.cn/wepe/WePE_64_V2.3.exe")
int main()
{
CWinHttpClient obj;
obj.AddRequestHeader(_T("Content-Type"), _T("application/json;charset=UTF-8"));
obj.AddRequestHeader(_T("Accept"), _T("*/*"));
obj.AddRequestHeader(_T("Referer"), _T("https://www.amd.com/"));
obj.SetAgent();
bool fResult;
clock_t startTime = ::clock();
for (int i = 0; i < 1; i++)
{
fResult = obj.DownloadFile(TEST_AMD_DRIVER, _T(""), [](const WINHTTP_PROGRESS_INFO& progress) {
CWinHttpClient::ConsoleOutput(_T("%d/%d Time: %.3lfs Progress: %.3lf%% %.1lfKB/%.1lfKB Speed: %.1lf Mbps %.1lfMB/s %.1lfKB/s %.1lfB/s RemainTime: %.1lfs\n"),
progress.nActiveThread, progress.nTotalThread,
(double)progress.costTime / 1000.0f,
progress.lfProgress * 100,
(double)progress.ullCur / (1024.0f), (double)progress.ullTotal / (1024.0f),
progress.lfSpeed / (1024.0f * 1024.0f) * 8.0f,
progress.lfSpeed / (1024.0f * 1024.0f),
progress.lfSpeed / (1024.0f),
progress.lfSpeed,
progress.lfRemainTime
);
return true;
}, 4
);
}
clock_t endTime = ::clock();
CWinHttpClient::ConsoleOutput(_T("Cost Time: %dms\r\n"), endTime - startTime);
return 0;
}