1

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!

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Lennard Fonteijn
  • 2,561
  • 2
  • 24
  • 39
  • You say the callback is reporting an error, but what is `WinHttpSendRequest()`/`WinHttpReceiveResponse()` itself reporting? Please show your actual code, not just a description of the code. – Remy Lebeau May 14 '16 at 23:57
  • @RemyLebeau Both return true, the error happens in between as part of the async callback. Here is the full code: http://pastebin.com/PZtdA7Y0 - main has a boolean to switch between sync and async. – Lennard Fonteijn May 15 '16 at 00:35
  • You have several memory leaks in that code. You are not freeing any of the buffers that you allocate. And your buffer allocations for `WinHttpReadData()` are just plain wrong. Why are you allocating `char*[]` arrays instead of `char[]` arrays? Also, you are calling `WinHttpSetStatusCallback()` even if `WinHttpOpenRequest()` fails or is even skipped, thus losing whatever error code was previously set before you call `GetLastError()`. And you are calling `WinHttpReceiveResponse()`, `WinHttpQueryDataAvailable()`, and `WinHttpReadData()` even if no request was sent due to a failure. – Remy Lebeau May 15 '16 at 03:36
  • @RemyLebeau Thanks for pointing those out, I'm aware, my professional background is C# and only rarely touched C++, so I'm a little rusty. I have just consolidated the issue in a separate program, so it does not have to be perfect, since the actual issue happens in a program I don't have the source off (I just know it does this all this after having used an API monitor). If I look at the Wine source of WinHTTP which should be relatively close, I can see the error happens right before it wants to send out WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE, but I have no idea why. – Lennard Fonteijn May 15 '16 at 10:37

0 Answers0