I'm playing around in C++ with WinHTTP.
I made an API in C# which I'm trying to call from C++, for this I'm using WinHTTP. The API is hosted on a local server with SSL active, the root certificate is also trusted.
I made a sample application to go through the following in order:
- WinHttpOpen with WINHTTP_FLAG_ASYNC
- WinHttpConnect with INTERNET_DEFAULT_HTTPS_PORT
- WinHttpOpenRequest with WINHTTP_FLAG_SECURE
- WinHttpSetStatusCallback to set callback function
- WinHttpSendRequest
- WinHttpReceiveResponse
- WinHttpQueryDataAvailable
- WinHttpReadData
- WinHttpCloseHandle on all open handles
Here is the full source:
#include <windows.h>
#include <winhttp.h>
#include <iostream>
#pragma comment(lib, "winhttp")
LPWSTR GetHeaders(HINTERNET hHttp)
{
LPVOID lpOutBuffer = NULL;
DWORD dwSize = 0;
retry:
if (!WinHttpQueryHeaders(hHttp, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, (LPVOID)lpOutBuffer, &dwSize, WINHTTP_NO_HEADER_INDEX))
{
if (GetLastError() == ERROR_WINHTTP_HEADER_NOT_FOUND)
{
std::cout << "ERROR_HTTP_HEADER_NOT_FOUND" << std::endl;
return NULL;
}
else
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
//std::cout << "ERROR_INSUFFICIENT_BUFFER" << std::endl;
lpOutBuffer = new wchar_t[dwSize];
goto retry;
}
else
{
std::cout << GetLastError() << std::endl;
if (lpOutBuffer)
{
delete[] lpOutBuffer;
}
return NULL;
}
}
}
return (LPWSTR)lpOutBuffer;
}
VOID CALLBACK HookCallback(
HINTERNET hInternet,
DWORD_PTR dwContext,
DWORD dwInternetStatus,
LPVOID lpvStatusInformation,
DWORD dwStatusInformationLength)
{
std::cout << "Status: " << dwInternetStatus << std::endl;
if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR)
{
WINHTTP_ASYNC_RESULT *pAR = (WINHTTP_ASYNC_RESULT*)lpvStatusInformation;
std::cout << "Error: " << pAR->dwError << std::endl;
}
else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE)
{
char *buffer = new char[dwStatusInformationLength+1];
memcpy(buffer, lpvStatusInformation, dwStatusInformationLength);
buffer[dwStatusInformationLength] = '\0';
std::cout << buffer << std::endl;
}
else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE)
{
BOOL result = WinHttpQueryDataAvailable(hInternet, NULL);
if (!result)
{
std::cout << GetLastError() << std::endl;
}
LPWSTR pwszHeaders = GetHeaders(hInternet);
char* headers = new char[wcslen(pwszHeaders) + 1];
wsprintfA(headers, "%S", pwszHeaders);
std::cout << headers << std::endl;
}
else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE)
{
DWORD *bytesAvailable = (DWORD *)lpvStatusInformation;
std::cout << *bytesAvailable << std::endl;
LPVOID buffer = (LPVOID)new char *[*bytesAvailable];
WinHttpReadData(hInternet, buffer, *bytesAvailable, NULL);
}
};
int main()
{
BOOL async = TRUE;
BOOL bResults = FALSE;
HINTERNET hSession = NULL,
hConnect = NULL,
hRequest = NULL;
// Use WinHttpOpen to obtain a session handle.
hSession = WinHttpOpen(L"WinHTTP/1.0",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
async ? WINHTTP_FLAG_ASYNC : 0);
// Specify an HTTP server.
if (hSession)
hConnect = WinHttpConnect(hSession, L"api.local", INTERNET_DEFAULT_HTTPS_PORT, 0);
// Create an HTTP Request handle.
if (hConnect)
hRequest = WinHttpOpenRequest(hConnect, L"GET",
L"/config",
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_SECURE);
if (async) {
WinHttpSetStatusCallback(hRequest, (WINHTTP_STATUS_CALLBACK)&HookCallback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
}
// Send a Request.
if (hRequest)
bResults = WinHttpSendRequest(hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0, WINHTTP_NO_REQUEST_DATA, 0,
0, 0);
// Report any errors.
if (!bResults)
std::cout << GetLastError() << std::endl;
bResults = WinHttpReceiveResponse(hRequest, NULL);
// Report any errors.
if (!bResults)
std::cout << GetLastError() << std::endl;
if (!async)
{
DWORD bytesAvailable;
bResults = WinHttpQueryDataAvailable(hRequest, &bytesAvailable);
// Report any errors.
if (!bResults)
std::cout << GetLastError() << std::endl;
std::cout << bytesAvailable << std::endl;
LPVOID buffer = (LPVOID)new char *[bytesAvailable];
bResults = WinHttpReadData(hRequest, buffer, bytesAvailable, NULL);
// Report any errors.
if (!bResults)
std::cout << GetLastError() << std::endl;
std::cout << (char *)buffer << std::endl;
// Close any open handles.
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
}
while (true)
{
Sleep(1000);
}
}
Whenever I call WinHttpSendRequest, my API is hit and a response is sent back. However, right before it should call the callback with WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE, it is triggered with an WINHTTP_CALLBACK_STATUS_REQUEST_ERROR pointing out a ERROR_INTERNET_OPERATION_CANCELLED. The error description says this usually occurs if the handle is closed prematurely. If I hook the CloseHandle with some assembly magic, I can see its indeed called right before the error.
Question: What might be potential causes that it's generating this error at this particular point in the chain? How can I get more information that this when my API seemingly returns its data just fine.
I would have to add that the API is a "proxy" to another API. As such it does it's own WebRequest internally for every request that comes in, then writes that result to the response. The weird thing is however, if I cache the result of the internal WebRequest and return that immediately the next time I try it, without ever initiating the WebRequest, the C++ proceeds as expected. As soon as I call the internal WebRequest, but still return the cached value (even though it's identical to what I'm requesting), C++ fails.
That said, I have excluded the possibility of timeouts being the issue, because the response happens with or without caching within 30ms.
Also, when I disable ASYNC, I get to read my response just fine regardless of what happens on the API side.
I'm really pulling my hairs over this!