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