Search code examples
c++httpwininet

Converting WinHttp to WinInet API POST request


I am trying to convert some HTTP request code from using the WinHttp COM interface to using lower-level WinInet calls from <wininet.h>. The COM version is working but I am having difficulty translating the calls into the WinInet API.

This code works fine and gets the correct error response (as the request data is empty) to the POST request:

#import <winhttpcom.dll>
#include <iostream>
#include <string>

int main()
{
    HRESULT hr = CoInitialize(NULL);

    using namespace WinHttp;
    IWinHttpRequestPtr pReq = NULL;

    hr = pReq.CreateInstance(__uuidof(WinHttpRequest));

    const char* pszReq = "";
    
    if (SUCCEEDED(hr))
    {
        _bstr_t bstrMethod("POST");
        _bstr_t bstrUrl("https://lite.realtime.nationalrail.co.uk/OpenLDBWS/ldb9.asmx");
        hr = pReq->Open(bstrMethod, bstrUrl);

        pReq->SetRequestHeader(_bstr_t("Content-Type"), _bstr_t("text/*"));

        _variant_t vReq(pszReq);
        hr = pReq->Send(vReq);
        if (SUCCEEDED(hr))
        {
            _bstr_t bstrResp;
            hr = pReq->get_ResponseText(&bstrResp.GetBSTR());
            if (SUCCEEDED(hr))
            {
                std::cout << std::string(bstrResp) << "\n";
            }
        }
    }
    CoUninitialize();
}

Saving the output as html, gives this rendering of the response (which is what I expect, since I haven't provided any request data, which would usually include an access token).

enter image description here

This is the code (amended after comments below, and should be reproducible) that I am using to try and replicate this result using wininet.h and the low-level Win32 calls (I realize I haven't closed the handles).

#include <windows.h>
#include <WinInet.h>   
#include <iostream>
   
int main()
{
    const char* pszReq = "";
    const char* pszUrl = "https://lite.realtime.nationalrail.co.uk/OpenLDBWS/ldb9.asmx";

    char szHostName[256];
    char szPath[256];

    URL_COMPONENTSA comps = {};
    comps.dwStructSize = sizeof(comps);
    comps.lpszHostName = szHostName;
    comps.dwHostNameLength = sizeof(szHostName);
    comps.lpszUrlPath = szPath;
    comps.dwUrlPathLength = sizeof(szPath);

    if (!InternetCrackUrlA(pszUrl, strlen(pszUrl), 0, &comps)) return 1;

    HINTERNET hOpen = InternetOpenA("XYZ",INTERNET_OPEN_TYPE_DIRECT,NULL,NULL,0);
    if (!hOpen) return 1;

    HINTERNET hConnect = InternetConnectA(hOpen,szHostName,comps.nPort,
            NULL,NULL,INTERNET_SERVICE_HTTP,0,NULL);
    if (!hConnect) return 1;

    const char * rgpszAcceptTypes[] = { "text/*", NULL };    
    HINTERNET hOpenReq = HttpOpenRequestA(hConnect,"POST",szPath,NULL, NULL,
                rgpszAcceptTypes, 0,NULL);    
    
    if (!hOpenReq) return 1;

    const char* pszHeader = "Content-Type: text/xml;charset=UTF-8";

    //*** This line returns FALSE ***
    BOOL bRet = HttpSendRequestA(hOpenReq, pszHeader, strlen(pszHeader), (LPVOID)pszReq, strlen(pszReq));
    //*** LastError is ERROR_HTTP_INVALID_SERVER_RESPONSE
    DWORD dwErr = GetLastError();

    return 0;
}

All the WinInet handles are non-zero, suggesting the calls are working, but the last HttpSendRequestA() is returning FALSE immediately, with LastError set to ERROR_HTTP_INVALID_SERVER_RESPONSE.

Clearly the COM route hides a lot of intermediate working, and presumably some constants are defaulted to specific values. It may also be adding other header information, I suppose.

Perhaps someone can suggest where I am going wrong?


Solution

  • There are some mistakes in your WinInet code:

    • the pszServerName value needs to be just the host name by itself, not a full URL. If you have a URL as input, you can parse it into its constituent pieces using InternetCrackUrlA().

    • the 3rd parameter of HttpOpenRequestA() is the requested resource relative to pszServerName. So, in your example, you need to use "/" to request the root resource.

    • the 1st parameter of HttpSendRequestA() needs to be hOpenReq, not hOpen. Also, you should not be including the null-terminators in your buffer sizes.

    If you have not already done so, you should have a look at WinInet's documentation on HTTP Sessions.

    With that said, try this:

    #include <windows.h>
    #include <WinInet.h>
    #include <iostream>
    
    const char * pszUrl = "https://someUrl";
    const char * pszReq = "A string of request data";
    const char* pszHeader = "Content-Type: text/xml;charset=UTF-8";
    
    char szHostName[256];
    char szPath[256];
    
    URL_COMPONENTSA comps = {};
    comps.dwStructSize = sizeof(comps);
    comps.lpszHostName = szHostName;
    comps.dwHostNameLength = sizeof(szHostName);
    comps.lpszUrlPath = szPath;
    comps.dwUrlPathLength = sizeof(szPath);
    
    BOOL bRet = InternetCrackUrlA(pszUrl, strlen(pszUrl), 0, &comps);
    if (!bRet) ...
    
    HINTERNET hOpen = InternetOpenA("XYZ", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    if (!hOpen) ...
    
    HINTERNET hConnect = InternetConnectA(hOpen, szHostName, comps.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL);
    if (!hConnect) ...
    
    HINTERNET hOpenReq = HttpOpenRequestA(hConnect, "POST", szPath, NULL, NULL, NULL, comps.nScheme == INTERNET_SCHEME_HTTPS ? INTERNET_FLAG_SECURE : 0, NULL);
    if (!hOpenReq) ...
    
    bRet = HttpSendRequestA(hOpenReq, pszHeader, strlen(pszHeader), pszReq, strlen(pszReq));
    if (!bRet) ...
    
    ...