WinInet和WinHttp是windows平台下提供了两套独立的网络库,按照微软官方的说法, WinInet的优势在于client-端的应用,而WinHttp更适用于server-端编程。从名称上我们可以看出WinHttp在Http协议应用方面要比WinInet更加专业,WinInet支持的协议包括Gopher\HTTP\HTTPS\FTP较为杂乱,而WinHttp库专门是为HTTP\HTTPS服务的。
在一个Session之上可以存在多个Connection对象,每个Connection对应于一个目标服务器和端口号。在一个Connection之上可以存在多个Request对象,每个Request对应于Connection之上的URI资源地址以及HTTP动作(GET或POST等)。
使用winhttp需要调用winhttp库(windows系统自带winhttp.lib和winhttp.dll)直接
#include <winhttp.h>
#pragma comment(lib,"winhttp.lib")
初始化winhttp
在使用WinHttp连接服务器之前,需要使用WinHttpOpen来进行初始化。WinHttpOpen将创建一个维护Http会话细节的会话环境(Session Context),并返回一个session handle。使用该句柄,WinHttpConnect可以连接到目标Http(Https)服务器。
WINHTTPAPI
HINTERNET
WINAPI
WinHttpOpen
(
__in_opt LPCWSTR pszAgentW,
__in DWORD dwAccessType,
__in_opt LPCWSTR pszProxyW,
__in_opt LPCWSTR pszProxyBypassW,
__in DWORD dwFlags
);
见:WinHttpOpen function (winhttp.h) - Win32 apps | Microsoft Docs
如:
HINTERNET hSession = WinHttpOpen(L"A WinHTTP Example Program/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
在与服务器交互之前, 必须用调用WinHttpOpen进行初始化,WinHttpOpen创建一个会话,并返回该会话的句柄,接着有了这个句柄.
BOOL WinHttpSetTimeouts
(
[in] HINTERNET hInternet,
[in] int nResolveTimeout,
[in] int nConnectTimeout,
[in] int nSendTimeout,
[in] int nReceiveTimeout
);
见:WinHttpSetTimeouts function (winhttp.h) - Win32 apps | Microsoft Docs
如:
HINTERNET hSession = WinHttpOpen(L"A WinHTTP Example Program/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession)
{
// Use WinHttpSetTimeouts to set a new time-out values.
if (!WinHttpSetTimeouts( hSession, 10000, 10000, 10000, 10000))
printf( "Error %u in WinHttpSetTimeouts.\n", GetLastError());
BOOL WinHttpSetOption(
[in] HINTERNET hInternet,
[in] DWORD dwOption,
[in] LPVOID lpBuffer,
[in] DWORD dwBufferLength
);
见:WinHttpSetOption function (winhttp.h) - Win32 apps | Microsoft Docs
参数的值见:Option Flags (Winhttp.h) - Win32 apps | Microsoft Docs
如:
DWORD dwFlags = SECURITY_FLAG_SECURE |
SECURITY_FLAG_IGNORE_UNKNOWN_CA |
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE |
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
WinHttpSetOption(m_hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags));
BOOL WinHttpQueryOption(
[in] HINTERNET hInternet,
[in] DWORD dwOption,
[out] LPVOID lpBuffer,
[in, out] LPDWORD lpdwBufferLength
);
见:WinHttpQueryOption function (winhttp.h) - Win32 apps | Microsoft Docs
其和上面的WinHttpSetOption函数基本一致。
连接目标服务器
需要指定服务器IP和端口号。
WINHTTPAPI
HINTERNET
WINAPI
WinHttpConnect
(
IN HINTERNET hSession,
IN LPCWSTR pswzServerName,
IN INTERNET_PORT nServerPort,
IN DWORD dwReserved //该值必须是0
);
pswzServerName:指向包含 HTTP 服务器的主机名的 以 null 结尾的字符串的指针
见:WinHttpConnect function (winhttp.h) - Win32 apps | Microsoft Docs
如
hConnect = WinHttpConnect( hSession, L"www.microsoft.com",INTERNET_DEFAULT_HTTPS_PORT, 0);
WinHttpConnect就能指定一个目标服务器.
注意:调用了WinHttpConnect并不意味着和服务器建立了真正的连接.
打开请求
在调用WinHttpOpenRequest时并不会向服务器发送请求。指定是get还是post以及网址后面的目录
WINHTTPAPI
HINTERNET
WINAPI
WinHttpOpenRequest
(
IN HINTERNET hConnect,
IN LPCWSTR pwszVerb, //GET 或者是POST
IN LPCWSTR pwszObjectName, //网址后面的目录
IN LPCWSTR pwszVersion, 如HTTP/1.1
IN LPCWSTR pwszReferrer OPTIONAL, 如WINHTTP_NO_REFERER
IN LPCWSTR FAR * ppwszAcceptTypes OPTIONAL, 如
IN DWORD dwFlags
);
pwszObjectName: 指向包含指定 HTTP 谓词的目标资源名称的字符串的指针。 这通常是文件名、可执行模块或搜索说明符。
见:WinHttpOpenRequest function (winhttp.h) - Win32 apps | Microsoft Docs
hRequest = WinHttpOpenRequest(hConnect, L"Get", L"/GetSanyNestTask?token=123", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
添加请求头部
即设置请求协议头信息
BOOLAPI
WinHttpAddRequestHeaders
(
IN HINTERNET hRequest,
IN LPCWSTR pwszHeaders,
IN DWORD dwHeadersLength,
IN DWORD dwModifiers
);
pwszHeaders: 指向字符串变量的指针,该变量包含要追加到请求的标头。 每个标头(最后一个除外)都必须由回车/换行符 (CR/LF) 终止。
见:WinHttpAddRequestHeaders function (winhttp.h) - Win32 apps | Microsoft Docs
LPCWSTR header = _T("Content-type: application/json; charset=utf-8/r/n");
SIZE_T len = lstrlenW(header);
bResults = WinHttpAddRequestHeaders(hRequest, header, DWORD(len), WINHTTP_ADDREQ_FLAG_ADD);
if(!bResults)
{
cout << "Error:Headers failed: " << endl;
return -1;
}
向服务器发送请求
BOOLAPI
WinHttpSendRequest
(
IN HINTERNET hRequest,
IN LPCWSTR pwszHeaders OPTIONAL, //该字符串包含要追加到请求的其他标头
IN DWORD dwHeadersLength,
IN LPVOID lpOptional OPTIONAL,//该缓冲区包含要紧接在请求标头之后发送的任何可选数 据此参数通常用于 POST 和 PUT 操作
IN DWORD dwOptionalLength,
IN DWORD dwTotalLength,
IN DWORD_PTR dwContext
);
见:WinHttpSendRequest function (winhttp.h) - Win32 apps | Microsoft Docs
string data = string(sendData);
const void *ss = (const char *)data.c_str();
//发送请求
BOOL bResults;
bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, const_cast<void*>(ss), data.length(), data.length(), 0);
if (!bResults){
cout << "Error:SendRequest failed: " << GetLastError() << endl;
return -1;
}
else{
//发送请求成功则准备接受服务器的response。注意:在使用 WinHttpQueryDataAvailable和WinHttpReadData前必须使用WinHttpReceiveResponse才能access服务器返回的数据
bResults = WinHttpReceiveResponse(hRequest, NULL);
}
WinHttpOpenRequest函数打开一个HTTP请求并返回一个HINTERNET 句柄,该句柄在接下来的步骤中会用到 , WinHttpOpenRequest并不会向服务器发送请求, 实际上是WinHttpSendRequest通过网络与服务器建立连接并发送请求.
向服务器发送数据
BOOLAPI
WinHttpWriteData
(
IN HINTERNET hRequest,
IN LPCVOID lpBuffer,
IN DWORD dwNumberOfBytesToWrite,
OUT LPDWORD lpdwNumberOfBytesWritten
);
见:WinHttpWriteData function (winhttp.h) - Win32 apps | Microsoft Docs
// Send a Request.
if (hRequest) bResults = WinHttpSendRequest( hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, (DWORD)strlen(pszData), 0);
// Write data to the server.
if (bResults) bResults = WinHttpWriteData( hRequest, pszData, (DWORD)strlen(pszData), &dwBytesWritten);
// End the request.
if (bResults) bResults = WinHttpReceiveResponse( hRequest, NULL);
获取响应
WINHTTPAPI
BOOL
WINAPI
WinHttpReceiveResponse
(
IN HINTERNET hRequest,
IN LPVOID lpReserved //必须是NULL
);
见:WinHttpReceiveResponse function (winhttp.h) - Win32 apps | Microsoft Docs
An application must call WinHttpReceiveResponse before it can use WinHttpQueryDataAvailable and WinHttpReadData to access the response entity body (if any).
所以其调用放在WinHttpSendRequest、WinHttpWriteData后,WinHttpQueryHeaders、WinHttpQueryDataAvailable前。
如bResults = WinHttpReceiveResponse( hRequest, NULL);
获取响应头信息
应用程序检索HTTP请求的信息,主要是得到响应头信息,包括响应头的长度
BOOL WinHttpQueryHeaders(
[in] HINTERNET hRequest,
[in] DWORD dwInfoLevel,
[in, optional] LPCWSTR pwszName,
[out] LPVOID lpBuffer,
[in, out] LPDWORD lpdwBufferLength,
[in, out] LPDWORD lpdwIndex
);
见:WinHttpQueryHeaders function (winhttp.h) - Win32 apps | Microsoft Docs
第二个参数dwInfoLevel 的设置见:Query Info Flags (Winhttp.h) - Win32 apps | Microsoft Docs
如:
int http_code = 0;
wchar_t szBuffer[24] = { 0 };
DWORD dwSize = 24 * sizeof(wchar_t);
if (::WinHttpQueryHeaders(m_hRequest, WINHTTP_QUERY_STATUS_CODE, WINHTTP_HEADER_NAME_BY_INDEX, szBuffer, &dwSize, WINHTTP_NO_HEADER_INDEX)) {
wchar_t *p = NULL;
http_code = wcstoul(szBuffer, &p, 10);
}
从服务器读取到响应报文的长度
获取响应报文的长度
BOOLAPI
WinHttpQueryDataAvailable
(
IN HINTERNET hRequest,
OUT __out_data_source(NETWORK) LPDWORD lpdwNumberOfBytesAvailable OPTIONAL
);
见:WinHttpQueryDataAvailable function (winhttp.h) - Win32 apps | Microsoft Docs
dwSize = 0; if (!WinHttpQueryDataAvailable( hRequest, &dwSize)) printf( "Error %u in WinHttpQueryDataAvailable.\n", GetLastError());
从服务器读取响应报文
BOOLAPI
WinHttpReadData
(
IN HINTERNET hRequest,
IN __out_data_source(NETWORK) LPVOID lpBuffer,
IN DWORD dwNumberOfBytesToRead,
OUT LPDWORD lpdwNumberOfBytesRead
);
见:WinHttpReadData function (winhttp.h) - Win32 apps | Microsoft Docs
ZeroMemory(pszOutBuffer, dwSize+1);
if (!WinHttpReadData( hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded))
{ printf( "Error %u in WinHttpReadData.\n", GetLastError()); }
关闭winhttp资源
BOOLAPI
WinHttpCloseHandle
(
IN HINTERNET hInternet
);
见:WinHttpCloseHandle function (winhttp.h) - Win32 apps | Microsoft Docs
http://xxx.yyy.zzz:8324/urlpath?pk1=pv1&pk2=pk2
get方式:
- xxx.yyy.zzz:8324 在WinHttpConnect的参数pswzServerName设置
- 如果需要传参数(pk1=pv1&pk2=pk2)的话,参数只能和urlpath合在一起,通过设置WinHttpOpenRequest的参数pwszObjectName传递。
Post方式:
- xxx.yyy.zzz:8324 在WinHttpConnect的参数pswzServerName设置
- urlpath在WinHttpOpenRequest的参数pwszObjectName设置。
- 设置协议头WinHttpAddRequestHeaders
如果协议头是Content-type: application/x-www-form-urlencoded
std::wstring wstrHeader[] = { L"Content-type: application/x-www-form-urlencoded\r\n"};
for ( size_t i = 0; i < ARRAYSIZE(wstrHeader); i++ ) {
WinHttpAddRequestHeaders(hRequest, wstrHeader[i].c_str(), wstrHeader[i].length(), WINHTTP_ADDREQ_FLAG_ADD);
}
如果协议头是Content-type: multipart/form-data
LPCWSTR additionalHeaders = L"nContent-Type: multipart/form-data; boundary=----------------------------346435246262465368257857\r\n";
WinHttpAddRequestHeaders(hRequest, additionalHeaders, -1L, WINHTTP_ADDREQ_FLAG_ADD);
4.调WinHttpSendRequest( m_hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, dwTotal, 0) dwTotal为需要传输数据的大小
如:Content-type: application/x-www-form-urlencoded
dwTotal的大小为:键值对(具体为?pk1=pv1&pk2=pk2)的长度
5.调WinHttpWriteData传输实际的数据。
完整一个例子
DWORD dwSize = 0;
DWORD dwDownloaded = 0;
LPSTR pszOutBuffer;
BOOL bResults = FALSE;
HINTERNET hSession = NULL,
hConnect = NULL,
hRequest = NULL;
// Use WinHttpOpen to obtain a session handle.
hSession = WinHttpOpen( L"WinHTTP Example/1.0",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
// Specify an HTTP server.
if (hSession)
hConnect = WinHttpConnect( hSession, L"www.microsoft.com",
INTERNET_DEFAULT_HTTPS_PORT, 0);
// Create an HTTP request handle.
if (hConnect)
hRequest = WinHttpOpenRequest( hConnect, L"GET", NULL,
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_SECURE);
// Send a request.
if (hRequest)
bResults = WinHttpSendRequest( hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0, WINHTTP_NO_REQUEST_DATA, 0,
0, 0);
// End the request.
if (bResults)
bResults = WinHttpReceiveResponse( hRequest, NULL);
// Continue to verify data until there is nothing left.
if (bResults)
do
{
// Verify available data.
dwSize = 0;
if (!WinHttpQueryDataAvailable( hRequest, &dwSize))
printf( "Error %u in WinHttpQueryDataAvailable.\n",
GetLastError());
// Allocate space for the buffer.
pszOutBuffer = new char[dwSize+1];
if (!pszOutBuffer)
{
printf("Out of memory\n");
dwSize=0;
}
else
{
// Read the Data.
ZeroMemory(pszOutBuffer, dwSize+1);
if (!WinHttpReadData( hRequest, (LPVOID)pszOutBuffer,
dwSize, &dwDownloaded))
printf( "Error %u in WinHttpReadData.\n", GetLastError());
else
printf( "%s\n", pszOutBuffer);
// Free the memory allocated to the buffer.
delete [] pszOutBuffer;
}
} while (dwSize > 0);
// Report any errors.
if (!bResults)
printf( "Error %d has occurred.\n", GetLastError());
// Close open handles.
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);