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 WinHttpCallback = std::function<bool(const WINHTTP_PROGRESS_INFO& progress)>;
class CWinHttpValue;
using CWinHttpObject = std::map<_tstring, CWinHttpValue>;
using CWinHttpArray = std::vector<CWinHttpValue>;
// WinHttp客户端参数类
class CWinHttpValue
{
public:
CWinHttpValue();
CWinHttpValue(bool val);
CWinHttpValue(int val);
CWinHttpValue(double val);
CWinHttpValue(const LPCTSTR val);
CWinHttpValue(const _tstring& val);
CWinHttpValue(const CWinHttpObject& val);
CWinHttpValue(const CWinHttpValue& val);
CWinHttpValue(const CWinHttpArray& val);
CWinHttpValue(CWinHttpValue&& val) noexcept;
~CWinHttpValue();
bool IsEmpty() const;
_tstring AsGetString() const;
_tstring AsJsonString() const;
_tstring AsHeaderString() const;
private:
_tstring _AsGetString() const;
_tstring _AsJsonString() const;
_tstring _AsHeaderString(int depth) const;
static void _DumpString(_tstring& append_str, const _tstring& text, bool flag_escape);
static bool _GetUnicodeString(_tstring& append_str, const TCHAR* data_ptr, const TCHAR** end_ptr);
private:
union _WIN_HTTP_VALUE_DATA
{
bool _Bool;
int _Int;
double _Float;
_tstring* _StringPtr;
CWinHttpObject* _ObjectPtr;
CWinHttpArray* _ArrayPtr;
}m_Data;
enum _WIN_HTTP_VALUE_TPYE
{
TypeNull,
TypeBool,
TypeInt,
TypeFloat,
TpyeString,
TpyeObject,
TpyeArray
}m_Type;
};
// WinHttp客户端辅助类
class CWinHttpClient
{
public:
CWinHttpClient();
~CWinHttpClient();
// 关闭
void Close();
// 发送 GET 请求
CWinHttpResult Get(const _tstring& url, const CWinHttpValue& param = {}, const CWinHttpValue& header = {}, WinHttpCallback cb = nullptr);
// 发送 POST 请求
CWinHttpResult Post(const _tstring& url, const CWinHttpValue& param = {}, const CWinHttpValue& header = {}, WinHttpCallback cb = nullptr);
// 发送 GET 请求(多线程)
CWinHttpResult GetEx(const _tstring& url, const CWinHttpValue& param = {}, const CWinHttpValue& header = {}, WinHttpCallback cb = nullptr, DWORD dwThreadCount = 1);
// 多线程下载文件
CWinHttpResult DownloadFile(const _tstring& url, const _tstring& path = _T(""), const CWinHttpValue& header = {}, WinHttpCallback cb = nullptr, DWORD dwThreadCount = 4);
// 设置用户代理字符串
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, ...);
// 编码Utf8字符串
static std::string EncoderFromUtf8(const std::string& strContent);
// 解码Utf8字符串
static std::string DecoderFromUtf8(const std::string& strContent);
private:
// 发送 GET 请求
CWinHttpResult _Get(const _tstring& url, WinHttpCallback cb = nullptr);
// 发送 POST 请求
CWinHttpResult _Post(const _tstring& url, WinHttpCallback cb = nullptr);
// 执行请求
CWinHttpResult _DoRequest(const _tstring& url, const _tstring& strMethod, const std::string& strParam);
// 执行请求(多线程)
CWinHttpResult _GetEx(const _tstring& url, WinHttpCallback cb = nullptr, DWORD dwThreadCount = 1);
CWinHttpResult _MultiThreadRequest(const _tstring& url, 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; // 请求句柄
WinHttpCallback m_cbProgress; // 进度回调
WHTTP_ASYNC_DATA m_AsyncData; // 异步信息
_tstring m_strAgent; // 代理字符串
_tstring m_strFilePath; // 保存文件路径
_tstring m_strUrl; // 请求链接
_tstring m_strHeader; // 请求头信息
_tstring m_strParam; // 请求参数
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>
#include <map>
std::map<int32_t, std::string> g_utf8Code;
static std::map<int32_t, std::string> GetUtf8Code();
typedef struct _WINHTTP_URL_INFO
{
std::wstring strScheme;
std::wstring strHostName;
std::wstring strUserName;
std::wstring strPassword;
std::wstring urlPath;
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);
urlPath.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->urlPath[0];
this->uc.dwUrlPathLength = (DWORD)urlPath.size();
this->uc.lpszExtraInfo = &this->strExtraInfo[0];
this->uc.dwExtraInfoLength = (DWORD)strExtraInfo.size();
}
catch (...)
{
}
}
}WINHTTP_URL_INFO, *PWINHTTP_URL_INFO;
CWinHttpValue::CWinHttpValue()
:
m_Data{ 0 },
m_Type(_WIN_HTTP_VALUE_TPYE::TypeNull)
{
}
CWinHttpValue::CWinHttpValue(bool val) : CWinHttpValue()
{
m_Data._Bool = val;
m_Type = _WIN_HTTP_VALUE_TPYE::TypeBool;
}
CWinHttpValue::CWinHttpValue(int val) : CWinHttpValue()
{
m_Data._Int = val;
m_Type = _WIN_HTTP_VALUE_TPYE::TypeInt;
}
CWinHttpValue::CWinHttpValue(double val) : CWinHttpValue()
{
m_Data._Float = val;
m_Type = _WIN_HTTP_VALUE_TPYE::TypeFloat;
}
CWinHttpValue::CWinHttpValue(const LPCTSTR val) : CWinHttpValue()
{
if (val)
{
m_Data._StringPtr = new (std::nothrow) _tstring(val);
}
m_Type = _WIN_HTTP_VALUE_TPYE::TpyeString;
}
CWinHttpValue::CWinHttpValue(const _tstring& val) : CWinHttpValue()
{
m_Data._StringPtr = new (std::nothrow) _tstring(val);
m_Type = _WIN_HTTP_VALUE_TPYE::TpyeString;
}
CWinHttpValue::CWinHttpValue(const CWinHttpObject& val) : CWinHttpValue()
{
m_Data._ObjectPtr = new (std::nothrow) CWinHttpObject(val);
m_Type = _WIN_HTTP_VALUE_TPYE::TpyeObject;
}
CWinHttpValue::CWinHttpValue(const CWinHttpArray& val) : CWinHttpValue()
{
m_Data._ArrayPtr = new (std::nothrow) CWinHttpArray(val);
m_Type = _WIN_HTTP_VALUE_TPYE::TpyeArray;
}
CWinHttpValue::CWinHttpValue(const CWinHttpValue& val) : CWinHttpValue()
{
if (_WIN_HTTP_VALUE_TPYE::TpyeString == val.m_Type && val.m_Data._StringPtr)
{
m_Data._StringPtr = new (std::nothrow) _tstring(*val.m_Data._StringPtr);
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeObject == val.m_Type && val.m_Data._ObjectPtr)
{
m_Data._ObjectPtr = new (std::nothrow) CWinHttpObject(*val.m_Data._ObjectPtr);
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeArray == val.m_Type && val.m_Data._ArrayPtr)
{
m_Data._ArrayPtr = new (std::nothrow) CWinHttpArray(*val.m_Data._ArrayPtr);
}
m_Type = val.m_Type;
}
CWinHttpValue::CWinHttpValue(CWinHttpValue&& val) noexcept
{
m_Data = val.m_Data;
m_Type = val.m_Type;
val.m_Data = { 0 };
val.m_Type = _WIN_HTTP_VALUE_TPYE::TypeBool;
}
CWinHttpValue::~CWinHttpValue()
{
if (_WIN_HTTP_VALUE_TPYE::TpyeString == m_Type)
{
delete m_Data._StringPtr;
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeObject == m_Type)
{
delete m_Data._ObjectPtr;
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeArray == m_Type)
{
delete m_Data._ArrayPtr;
}
m_Data = { 0 };
m_Type = _WIN_HTTP_VALUE_TPYE::TypeBool;
}
bool CWinHttpValue::IsEmpty() const
{
if (_WIN_HTTP_VALUE_TPYE::TpyeObject == m_Type && m_Data._ObjectPtr)
{
return m_Data._ObjectPtr->empty();
}
return false;
}
_tstring CWinHttpValue::AsGetString() const
{
_tstring strResult;
if (IsEmpty())
{
return strResult;
}
return _AsGetString();
}
_tstring CWinHttpValue::AsJsonString() const
{
return _AsJsonString();
}
_tstring CWinHttpValue::AsHeaderString() const
{
return _AsHeaderString(0);
}
bool CWinHttpValue::_GetUnicodeString(_tstring& append_str, const TCHAR* data_ptr, const TCHAR** end_ptr)
{
TCHAR ch = *data_ptr;
#ifdef _UNICODE
_tchar text_buffer[32] = { 0 };
_json_stprintf_s(text_buffer, sizeof(text_buffer) / sizeof(_tchar), _T(R"(\u%.4x)"), ch);
append_str += text_buffer;
data_ptr++;
#else
if (ch >= 0xC0)
{
// The number of bytes used to obtain characters.
size_t byte_count = 0;
uint32_t cp32 = 0;
if (ch >= 0xE0 && ch <= 0xEF)
{
byte_count = 3;
cp32 = ch & 0x0F;
}
else if (ch >= 0xC0 && ch <= 0xDF)
{
byte_count = 2;
cp32 = ch & 0x1F;
}
else if (ch >= 0xF0 && ch <= 0xF7)
{
byte_count = 4;
cp32 = ch & 0x07;
}
else if (ch >= 0xF8 && ch <= 0xFB)
{
byte_count = 5;
cp32 = ch & 0x03;
}
else if (ch >= 0xFC && ch <= 0xFD)
{
byte_count = 6;
cp32 = ch & 0x01;
}
if (0 == byte_count)
{
return false;
}
for (size_t i = 1; i < byte_count; i++)
{
cp32 = cp32 << 6;
cp32 |= data_ptr[i] & 0x3F;
}
char text_buffer[32] = { 0 };
if (cp32 < 0x10000)
{
snprintf(text_buffer, sizeof(text_buffer), R"(\u%.4x)", cp32);
append_str.append(text_buffer, 6);
}
else
{
uint32_t cp = (uint16_t)(cp32 - 0x10000);
uint16_t cp32_high = (uint16_t)(cp >> 10) + 0xD800;
uint16_t cp32_low = (uint16_t)(cp & 0x3FF) + 0xDC00;
snprintf(text_buffer, sizeof(text_buffer), R"(\u%.4x\u%.4x)", cp32_high, cp32_low);
append_str.append(text_buffer, 12);
}
data_ptr += byte_count;
}
#endif
if (end_ptr)
{
*end_ptr = data_ptr;
}
return false;
}
void CWinHttpValue::_DumpString(_tstring& append_str, const _tstring& text, bool flag_escape)
{
const TCHAR* data_ptr = text.c_str();
while (_T('\0') != *data_ptr)
{
TCHAR ch = *data_ptr;
switch (ch)
{
case _T('\b'):
{
append_str += _T(R"(\b)");
}
break;
case _T('\t'):
{
append_str += _T(R"(\t)");
}
break;
case _T('\n'):
{
append_str += _T(R"(\n)");
}
break;
case _T('\f'):
{
append_str += _T(R"(\f)");
}
break;
case _T('\r'):
{
append_str += _T(R"(\r)");
}
break;
case _T('\"'):
{
append_str += _T(R"(\")");
}
break;
case _T('/'):
{
append_str += _T(R"(/)");
}
case _T('\\'):
{
append_str += _T(R"(\\)");
}
break;
default:
{
if (ch < 0x80 || !flag_escape)
{
append_str.push_back(ch);
}
else
{
_GetUnicodeString(append_str, data_ptr, &data_ptr);
continue;
}
}
break;
}
data_ptr++;
}
}
_tstring CWinHttpValue::_AsGetString() const
{
_tstring strResult;
if (_WIN_HTTP_VALUE_TPYE::TypeBool == m_Type)
{
strResult = m_Data._Bool ? _T("true") : _T("false");
}
else if (_WIN_HTTP_VALUE_TPYE::TypeInt == m_Type)
{
TCHAR szBuf[MAX_PATH] = { 0 };
::StringCchPrintf(szBuf, _countof(szBuf), _T("%d"), m_Data._Int);
strResult = szBuf;
}
else if (_WIN_HTTP_VALUE_TPYE::TypeFloat == m_Type)
{
TCHAR szBuf[MAX_PATH] = { 0 };
::StringCchPrintf(szBuf, _countof(szBuf), _T("%.16g"), m_Data._Float);
strResult = szBuf;
TCHAR* chPtr = szBuf;
bool flag_dot = false;
bool flag_exponent = false;
while (_T('\0') != *chPtr)
{
TCHAR ch = *chPtr;
if (_T('.') == ch)
{
flag_dot = true;
}
else if (_T('e') == ch)
{
flag_exponent = true;
}
chPtr++;
}
if (!flag_dot && 0 == !flag_exponent)
{
strResult += _T(".0");
}
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeString == m_Type)
{
strResult = *m_Data._StringPtr;
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeObject == m_Type)
{
const CWinHttpObject& object = *m_Data._ObjectPtr;
size_t size = object.size();
if (!object.empty())
{
strResult += _T("?");
for (const auto& item : object)
{
strResult += item.first;
strResult += _T("=");
strResult += item.second._AsGetString();
size--;
if (size > 0)
{
strResult += _T("&");
}
}
}
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeArray == m_Type)
{
const CWinHttpArray& object = *m_Data._ArrayPtr;
size_t size = object.size();
for (const auto& item : object)
{
strResult += item._AsGetString();
size--;
if (size > 0)
{
strResult += _T(",");
}
}
}
return strResult;
}
_tstring CWinHttpValue::_AsJsonString() const
{
_tstring strResult;
if (_WIN_HTTP_VALUE_TPYE::TypeBool == m_Type)
{
strResult = m_Data._Bool ? _T("true") : _T("false");
}
else if (_WIN_HTTP_VALUE_TPYE::TypeInt == m_Type)
{
TCHAR szBuf[MAX_PATH] = { 0 };
::StringCchPrintf(szBuf, _countof(szBuf), _T("%d"), m_Data._Int);
strResult = szBuf;
}
else if (_WIN_HTTP_VALUE_TPYE::TypeFloat == m_Type)
{
TCHAR szBuf[MAX_PATH] = { 0 };
::StringCchPrintf(szBuf, _countof(szBuf), _T("%.16g"), m_Data._Float);
strResult = szBuf;
TCHAR* chPtr = szBuf;
bool flag_dot = false;
bool flag_exponent = false;
while (_T('\0') != *chPtr)
{
TCHAR ch = *chPtr;
if (_T('.') == ch)
{
flag_dot = true;
}
else if (_T('e') == ch)
{
flag_exponent = true;
}
chPtr++;
}
if (!flag_dot && 0 == !flag_exponent)
{
strResult += _T(".0");
}
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeString == m_Type)
{
strResult += _T("\"");
_DumpString(strResult, *m_Data._StringPtr, false);
strResult += _T("\"");
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeObject == m_Type)
{
const CWinHttpObject& object = *m_Data._ObjectPtr;
size_t size = object.size();
if (!object.empty())
{
strResult += _T("{");
for (const auto& item : object)
{
strResult += _T("\"");
_DumpString(strResult, item.first, false);
strResult += _T("\"");
strResult += _T(":");
strResult += item.second._AsJsonString();
size--;
if (size > 0)
{
strResult += _T(",");
}
}
strResult += _T("}");
}
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeArray == m_Type)
{
const CWinHttpArray& object = *m_Data._ArrayPtr;
size_t size = object.size();
strResult += _T("[");
for (const auto& item : object)
{
strResult += item._AsJsonString();
size--;
if (size > 0)
{
strResult += _T(",");
}
}
strResult += _T("]");
}
return strResult;
}
_tstring CWinHttpValue::_AsHeaderString(int depth) const
{
_tstring strResult;
if (_WIN_HTTP_VALUE_TPYE::TypeBool == m_Type)
{
strResult = m_Data._Bool ? _T("true") : _T("false");
}
else if (_WIN_HTTP_VALUE_TPYE::TypeInt == m_Type)
{
TCHAR szBuf[MAX_PATH] = { 0 };
::StringCchPrintf(szBuf, _countof(szBuf), _T("%d"), m_Data._Int);
strResult = szBuf;
}
else if (_WIN_HTTP_VALUE_TPYE::TypeFloat == m_Type)
{
TCHAR szBuf[MAX_PATH] = { 0 };
::StringCchPrintf(szBuf, _countof(szBuf), _T("%.16g"), m_Data._Float);
strResult = szBuf;
TCHAR* chPtr = szBuf;
bool flag_dot = false;
bool flag_exponent = false;
while (_T('\0') != *chPtr)
{
TCHAR ch = *chPtr;
if (_T('.') == ch)
{
flag_dot = true;
}
else if (_T('e') == ch)
{
flag_exponent = true;
}
chPtr++;
}
if (!flag_dot && 0 == !flag_exponent)
{
strResult += _T(".0");
}
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeString == m_Type)
{
strResult += *m_Data._StringPtr;
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeObject == m_Type)
{
const CWinHttpObject& object = *m_Data._ObjectPtr;
size_t size = object.size();
if (!object.empty())
{
for (const auto& item : object)
{
strResult += item.first;
strResult += _T(": ");
strResult += item.second._AsHeaderString(depth + 1);
size--;
if (size > 0)
{
strResult += _T("\r\n");
}
}
}
}
else if (_WIN_HTTP_VALUE_TPYE::TpyeArray == m_Type)
{
const CWinHttpArray& object = *m_Data._ArrayPtr;
size_t size = object.size();
for (const auto& item : object)
{
strResult += item._AsHeaderString(depth + 1);
size--;
if (size > 0)
{
strResult += _T(", ");
}
}
}
return strResult;
}
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);
return;
}
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;
}
std::string FormatA(LPCSTR pFormat, ...)
{
size_t nCchCount = MAX_PATH;
std::string strResult(nCchCount, 0);
va_list args;
va_start(args, pFormat);
do
{
//成功则赋值字符串并终止循环
int nSize = _vsnprintf_s(&strResult[0], nCchCount, _TRUNCATE, pFormat, args);
if (-1 != nSize)
{
strResult.resize(nSize);
break;
}
//缓冲大小超限终止
if (nCchCount >= INT32_MAX)
{
break;
}
//重新分配缓冲
nCchCount *= 2;
strResult.resize(nCchCount);
} while (true);
va_end(args);
return strResult;
}
std::map<int32_t, std::string> GetUtf8Code()
{
std::map<int32_t, std::string> mapCode;
uint8_t szBuf[MAX_PATH] = { 0 };
for (uint32_t i = 0x4E00; i <= 0x9FFF; i++)
{
// 1字节
// 0xxxxxxx
if (i >= 0x00000000 && i <= 0x0000007F)
{
szBuf[0] = i;
szBuf[1] = 0;
}
// 2字节
// 110xxxxx 10xxxxxx
if (i >= 0x00000080 && i <= 0x000007FF)
{
szBuf[0] = ((i >> 6) & 0x1F) | 0xC0;
szBuf[1] = ( i & 0x3F) | 0x80;
szBuf[2] = 0;
}
// 3字节
// 1110xxxx 10xxxxxx 10xxxxxx
if (i >= 0x00000800 && i <= 0x0000FFFF)
{
szBuf[0] = ((i >> 12) & 0x0F) | 0xE0;
szBuf[1] = ((i >> 6) & 0x3F) | 0x80;
szBuf[2] = (i & 0x3F) | 0x80;
szBuf[3] = 0;
}
mapCode.insert(std::make_pair(i, (char*)szBuf));
}
return mapCode;
}
bool IsUtf8String(const std::string& strContent)
{
bool fResult = false;
size_t nLength = strContent.size();
const char* pStr = strContent.c_str();
bool fAscii = true;
int nBytes = 0;
for (int i = 0; i < nLength; i++)
{
char ch = pStr[i];
if (0 != (ch & 0x80))
{
fAscii = false;
}
if (ch >= 0x80)
{
// 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
if (ch >= 0xFC && ch <= 0xFD)
{
nBytes = 6;
}
// 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
else if (ch >= 0xF8 && ch <= 0xFB)
{
nBytes = 5;
}
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
else if (ch >= 0xF0 && ch <= 0xF7)
{
nBytes = 4;
}
// 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
else if (ch >= 0xE0 && ch <= 0xEF)
{
nBytes = 3;
}
// 110xxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
else if (ch >= 0xC0 && ch <= 0xDF)
{
nBytes = 2;
}
else
{
return FALSE;
}
nBytes--;
}
}
return fResult;
}
std::string CWinHttpClient::EncoderFromUtf8(const std::string& strContent)
{
std::string strResult;
return strResult;
}
std::string CWinHttpClient::DecoderFromUtf8(const std::string& strContent)
{
std::string strResult;
strResult.reserve(strContent.capacity());
size_t nLength = strContent.size();
const char* pStr = strContent.c_str();
for (int i = 0; i < nLength; i++)
{
// 转义解码
if (('\\' == pStr[i]))
{
// Unicode 字符
if ((i + 5) < nLength && 'u' == pStr[i + 1])
{
char szCode[8] = { 0 };
szCode[0] = pStr[i + 2];
szCode[1] = pStr[i + 3];
szCode[2] = pStr[i + 4];
szCode[3] = pStr[i + 5];
uint32_t uCode = _strtoui64(szCode, nullptr, 16);
// 1字节
// 0xxxxxxx
if (uCode >= 0x00000000 && uCode <= 0x0000007F)
{
szCode[0] = uCode;
szCode[1] = 0;
}
// 2字节
// 110xxxxx 10xxxxxx
if (uCode >= 0x00000080 && uCode <= 0x000007FF)
{
szCode[0] = ((uCode >> 6) & 0x1F) | 0xC0;
szCode[1] = ( uCode & 0x3F) | 0x80;
szCode[2] = 0;
}
// 3字节
// 1110xxxx 10xxxxxx 10xxxxxx
if (uCode >= 0x00000800 && uCode <= 0x0000FFFF)
{
szCode[0] = ((uCode >> 12) & 0x0F) | 0xE0;
szCode[1] = ((uCode >> 6) & 0x3F) | 0x80;
szCode[2] = (uCode & 0x3F) | 0x80;
szCode[3] = 0;
}
// 4字节
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
if (uCode >= 0x00010000 && uCode <= 0x001FFFFF)
{
szCode[0] = ((uCode >> 18) & 0x07) | 0xF0;
szCode[1] = ((uCode >> 12) & 0x3F) | 0x80;
szCode[2] = ((uCode >> 6) & 0x3F) | 0x80;
szCode[3] = (uCode & 0x3F) | 0x80;
szCode[4] = 0;
}
// 5字节
// 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
if (uCode >= 0x00200000 && uCode <= 0x03FFFFFF)
{
szCode[0] = ((uCode >> 24) & 0x03) | 0xF8;
szCode[1] = ((uCode >> 18) & 0x3F) | 0x80;
szCode[2] = ((uCode >> 12) & 0x3F) | 0x80;
szCode[3] = ((uCode >> 6) & 0x3F) | 0x80;
szCode[4] = (uCode & 0x3F) | 0x80;
szCode[5] = 0;
}
// 6字节
// 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
if (uCode >= 0x04000000 && uCode <= 0x7FFFFFFF)
{
szCode[0] = ((uCode >> 30) & 0x01) | 0xFC;
szCode[1] = ((uCode >> 24) & 0x3F) | 0x80;
szCode[2] = ((uCode >> 18) & 0x3F) | 0x80;
szCode[3] = ((uCode >> 12) & 0x3F) | 0x80;
szCode[4] = ((uCode >> 6) & 0x3F) | 0x80;
szCode[5] = (uCode & 0x3F) | 0x80;
szCode[6] = 0;
}
strResult += szCode;
i += 5;
}
// 转义字符
switch (pStr[i + 1])
{
case '\"':
{
strResult.push_back('\"');
i += 1;
}
break;
case '/':
{
strResult.push_back('\"');
i += 1;
}
break;
case 'b':
{
strResult.push_back('\b');
i += 1;
}
break;
case 'f':
{
strResult.push_back('\f');
i += 1;
}
break;
case 't':
{
strResult.push_back('\t');
i += 1;
}
break;
case 'r':
{
strResult.push_back('\r');
i += 1;
}
break;
case 'n':
{
strResult.push_back('\n');
i += 1;
}
break;
case '<':
{
strResult.push_back('<');
i += 1;
}
break;
case '>':
{
strResult.push_back('>');
i += 1;
}
break;
case '&':
{
strResult.push_back('&');
i += 1;
}
break;
}
}
else
{
strResult.push_back(pStr[i]);
}
}
strResult.push_back('\0');
return strResult;
}
static bool _CrackUrl(const _tstring& url, 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(url).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->urlPath.resize(wcslen(lpUrlInfo->urlPath.c_str()));
if (!lpUrlInfo->strExtraInfo.empty())
{
lpUrlInfo->urlPath += lpUrlInfo->strExtraInfo;
}
// 协议检查
if (!(INTERNET_SCHEME_HTTPS == lpUrlInfo->uc.nScheme || INTERNET_SCHEME_HTTP == lpUrlInfo->uc.nScheme))
{
return false;
}
return true;
}
CWinHttpResult CWinHttpClient::Get(const _tstring& url, const CWinHttpValue& param/* = {}*/, const CWinHttpValue& header/* = {}*/, WinHttpCallback cb/* = nullptr*/)
{
m_strFilePath.clear();
m_cbProgress = cb;
m_strUrl = url;
m_strParam = param.AsGetString();
m_strHeader = header.AsHeaderString();
return _Get(url, cb);
}
CWinHttpResult CWinHttpClient::Post(const _tstring& url, const CWinHttpValue& param/* = {}*/, const CWinHttpValue& header/* = {}*/, WinHttpCallback cb/* = nullptr*/)
{
m_strFilePath.clear();
m_cbProgress = cb;
m_strUrl = url;
m_strParam = param.AsJsonString();
m_strHeader = header.AsHeaderString();
return _Post(url, cb);
}
CWinHttpResult CWinHttpClient::GetEx(const _tstring& url, const CWinHttpValue& param/* = {}*/, const CWinHttpValue& header/* = {}*/, WinHttpCallback cb/* = nullptr*/, DWORD dwThreadCount/* = 1*/)
{
m_strFilePath.clear();
m_cbProgress = cb;
m_strUrl = url;
m_strParam = param.AsJsonString();
m_strHeader = header.AsHeaderString();
return _GetEx(url, cb, dwThreadCount);
}
CWinHttpResult CWinHttpClient::DownloadFile(const _tstring& url, const _tstring& strFile, const CWinHttpValue& header/* = {}*/, WinHttpCallback cb/* = nullptr*/, DWORD dwThreadCount/* = 4*/)
{
m_strFilePath = strFile;
m_strUrl = url;
m_strParam.clear();
m_strHeader = header.AsHeaderString();
if (m_strFilePath.empty())
{
size_t nPos = url.find_last_of(_T("/"));
if (_tstring::npos != nPos)
{
m_strFilePath = url.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(url, cb, dwThreadCount);
return httpResult;
}
CWinHttpResult CWinHttpClient::_Get(const _tstring& url, WinHttpCallback cb/* = nullptr*/)
{
_tstring urlAddr = url;
if (!m_strParam.empty())
{
urlAddr += m_strParam;
}
return _DoRequest(urlAddr, _T("GET"), "");
}
CWinHttpResult CWinHttpClient::_Post(const _tstring& url, WinHttpCallback cb/* = nullptr*/)
{
std::string strParam = TStrToU8Str(m_strParam);
return _DoRequest(url, _T("POST"), strParam);
}
CWinHttpResult CWinHttpClient::_DoRequest(const _tstring& url, const _tstring& strMethod, const std::string& strParam)
{
WINHTTP_URL_INFO urlInfo;
CWinHttpResult httpResult;
if (!_CrackUrl(url, &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.urlPath.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;
}
// 发送请求
if (!_WinHttpSendRequest(m_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& url, WinHttpCallback cb/* = nullptr*/, DWORD dwThreadCount/* = 6*/)
{
WINHTTP_URL_INFO urlInfo;
CWinHttpResult httpResult;
if (!_CrackUrl(url, &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.urlPath.c_str(), NULL, pwszReferrer, pwszAcceptTypes, dwRequestFlags))
{
_PrintError(_T("_WinHttpOpenRequest"));
break;
}
// 设置请求超时
if (!_WinHttpSetRequestTimeouts(1000, 1000, 3000, 5000))
{
_PrintError(_T("WinHttpSetTimeouts"));
break;
}
// 发送请求
if (!_WinHttpSendRequest(m_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(url, ullContentLength, dwThreadCount);
}
else
{
// 单线程接收请求数据
if (!_InternetReadData(httpResult.result))
{
_PrintError(_T("_InternetReadData"));
break;
}
}
} while (false);
// 关闭相关句柄
Close();
return httpResult;
}
CWinHttpResult CWinHttpClient::_MultiThreadRequest(const _tstring& url, 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_strHeader = m_strHeader;
vTaskClients[i].m_strParam = m_strParam;
vTaskClients[i].m_strUrl = m_strUrl;
vTaskClients[i].m_strFilePath = m_strFilePath;
vTaskClients[i].m_fPrint = m_fPrint;
vTaskThreads.emplace_back(std::thread([&vTaskClients, &vTaskResults, i, url, &nTaskCount, &fQuit, this]() {
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(url, [](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::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;
}
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;
CWinHttpValue header = CWinHttpObject{
{_T("User-Agent"), _T("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0")},
{_T("Content-Type"), _T("application/json;charset=UTF-8")},
{_T("Accept"), _T("*/*")},
{_T("Referer"), _T("https://www.amd.com/")},
};
CWinHttpValue param = CWinHttpObject{
{_T("Accept"), _T("application/x-clarity-gzip")},
{_T("Accept-Encoding"), CWinHttpArray {_T("gzip"), _T("deflate"), _T("br"), _T("zstd")}},
{_T("Accept-Language"), CWinHttpArray {_T("zh-CN"), _T("zh;q=0.9"), _T("en;q=0.8"), _T("en-GB;q=0.7"), _T("en-US;q=0.6")}},
};
_tstring strGet = param.AsGetString();
_tstring strJsonParam = param.AsJsonString();
_tstring strHeader = param.AsHeaderString();
CWinHttpResult get = obj.Get(_T(R"(https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory?roomid=4412054)"), {}, header, nullptr);
get.result = obj.DecoderFromUtf8(get.result);
std::string strResult = CWinHttpClient::DecoderFromUtf8(R"({"string":"\uD83C\uDF0D\u6211\u662F\u5730\u7403"})");
bool fResult;
clock_t startTime = ::clock();
CWinHttpResult downResult = obj.DownloadFile(TEST_AMD_DRIVER, _T(""), header.AsHeaderString(), [](const WINHTTP_PROGRESS_INFO& progress) {
CWinHttpClient::ConsoleOutput(_T("%d/%d Time: %.3lfs Progress: %.3lf%% %.1lfMB/%.1lfMB Speed: %.1lf Mbps %.1lfMB/s RemainTime: %.1lfs\n"),
progress.nActiveThread, progress.nTotalThread,
(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.lfRemainTime
);
return true;
}, 2
);
clock_t endTime = ::clock();
CWinHttpClient::ConsoleOutput(_T("Cost Time: %dms\r\n"), endTime - startTime);
return 0;
}