Невозможно получить ответ на запрос POST, используя wininet

Мне нужно сделать запрос POST к API, чтобы получить некоторые данные XML ( http://freecite.library.brown.edu/welcome/api_instructions). Это прекрасно работает с curl:

curl -H "Accept: application/xml" --data "citation=Udvarhelyi, I.S., Gatsonis, C.A., Epstein, A.M., Pashos, C.L., Newhouse, J.P. and McNeil, B.J. Acute Myocardial Infarction in the Medicare population: process of care and clinical outcomes. Journal of the American Medical Association, 1992; 18:2530-2536. " http://freecite.library.brown.edu:80/citations/create

Поэтому я пытаюсь сделать то же самое, используя Win32 SDK. Это мой код:

void LoadData()
{
    wil::unique_hinternet hInternet(InternetOpen(L"Dummy", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0));
    wil::unique_hinternet hConnect(InternetConnect(hInternet.get(), L"http://freecite.library.brown.edu", 80, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0));
    wil::unique_hinternet hRequest(HttpOpenRequest(hConnect.get(), L"POST", L"/citations/create", NULL, NULL, NULL, NULL, NULL));
    wstring data = L"citation=Udvarhelyi, I.S., Gatsonis, C.A., Epstein, A.M., Pashos, C.L., Newhouse, J.P. and McNeil, B.J. Acute Myocardial Infarction in the Medicare population: process of care and clinical outcomes. Journal of the American Medical Association, 1992; 18:2530-2536.";
    PCWSTR szHeaders = L"Accept: application/xml";

    HttpSendRequest(hRequest.get(), szHeaders, 0, (LPVOID)data.c_str(), static_cast<int>(data.length()));

    DWORD availableBytes = 0;
    InternetQueryDataAvailable(hRequest.get(), &availableBytes, 0, 0);
    PBYTE outputBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, availableBytes);
    PBYTE nextBytes = outputBuffer;
    DWORD bytesUsed = 0; // number of used bytes.
    while (availableBytes)
    {
        DWORD downloadedBytes;
        InternetReadFile(hRequest.get(), nextBytes, availableBytes, &downloadedBytes);
        bytesUsed = bytesUsed + downloadedBytes;

        InternetQueryDataAvailable(hRequest.get(), &availableBytes, 0, 0);
        if (availableBytes > 0)
        {
            // lazy buffer growth here. Only alloc for what we need. could be optimized if we end up with huge payloads (>10MB).
            // otherwise, this is good enough.
            outputBuffer = (PBYTE)HeapReAlloc(GetProcessHeap(), 0, outputBuffer, bytesUsed + availableBytes);
            nextBytes = outputBuffer + bytesUsed; // careful, pointer might have moved! Update cursor.
        }
    }

    // Convert outputed XML to wide char
    int size_needed = MultiByteToWideChar(CP_UTF8, 0, (PCCH)outputBuffer, bytesUsed, NULL, 0);
    std::wstring wstrTo(size_needed, 0);
    MultiByteToWideChar(CP_UTF8, 0, (PCCH)outputBuffer, bytesUsed, &wstrTo[0], size_needed);

    wstring res = wstrTo;
}

Проблема в том, прежде чем войти в for цикл, даже после вызова InternetQueryDataAvailable, availableBytes Получается равным 0. В результате я в итоге получаю пустую строку в качестве ответа, тогда как я ожидал ответа XML.

Может кто-нибудь указать мне, что я делаю неправильно, и как это исправить?

1 ответ

Решение

InternetConnect ожидает имя сервера или IP-адрес, поэтому не включайте "http://" в адрес. Изменить на:

InternetConnect(handle, L"freecite.library.brown.edu"...);

Используйте UTF-8 для data, Другие параметры для функций WinAPI правильно используют UTF-16, они автоматически производят необходимые преобразования.

Изменить заголовок:

std::wstring szHeaders = L"Content-Type: application/x-www-form-urlencoded\r\n";

accept должны быть отправлены через HttpOpenRequest

const wchar_t *accept[] = { L"text/xml", NULL };
HINTERNET hrequest = HttpOpenRequest(hconnect, L"POST", L"/citations/create",
    NULL, NULL, accept, 0, 0);

Обратите внимание, если вы не укажете accept (использование NULL на своем месте), то результат может быть в виде простого HTML.

Пример ниже должен возвращать данные XML

std::wstring get_utf16(const std::string &str);
std::string get_utf8(const std::wstring &wstr);

int main()
{
    HINTERNET hsession = NULL;
    HINTERNET hconnect = NULL;
    HINTERNET hrequest = NULL;
    std::wstring result;
    std::wstring szHeaders = L"Content-Type: application/x-www-form-urlencoded\r\n";
    std::wstring data_w = L"citation=Udvarhelyi, I.S., Gatsonis, C.A., Epstein, A.M., Pashos, C.L., Newhouse, J.P. and McNeil, B.J. Acute Myocardial Infarction in the Medicare population: process of care and clinical outcomes. Journal of the American Medical Association, 1992; 18:2530-2536.";
    std::string data = get_utf8(data_w);

    hsession = InternetOpen(L"appname", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    if(!hsession)
        goto cleanup;

    hconnect = InternetConnect(hsession, L"freecite.library.brown.edu",
        INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
    if(!hconnect)
        goto cleanup;

    const wchar_t *accept[] = { L"text/xml", NULL };
    hrequest = HttpOpenRequest(hconnect, L"POST", L"/citations/create",
        NULL, NULL, accept, 0, 0);
    if(!hrequest)
        goto cleanup;
    BOOL sent = HttpSendRequest(hrequest, szHeaders.c_str(), szHeaders.size(),
        &data[0], data.size());

    if(sent)
    {
        DWORD blocksize = 4096;
        DWORD received = 0;
        std::string temp;
        std::string block(blocksize, 0);
        while(InternetReadFile(hrequest, &block[0], blocksize, &received) && received)
        {
            block.resize(received);
            temp += block;
        }
        result = get_utf16(temp);
        std::wcout << result << std::endl;
    }

cleanup:
    if(hrequest)  InternetCloseHandle(hrequest);
    if(hconnect)  InternetCloseHandle(hconnect);
    if(hsession)  InternetCloseHandle(hsession);
    return 0;
}

std::wstring get_utf16(const std::string &str)
{
    if(str.empty()) return std::wstring();
    int sz = MultiByteToWideChar(CP_UTF8, 0, &str[0], -1, 0, 0);
    std::wstring res(sz, 0);
    MultiByteToWideChar(CP_UTF8, 0, &str[0], -1, &res[0], sz);
    return res;
}

std::string get_utf8(const std::wstring &wstr)
{
    int sz = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], -1, 0, 0, 0, 0);
    std::string res(sz, 0);
    WideCharToMultiByte(CP_UTF8, 0, &wstr[0], -1, &res[0], sz, 0, 0);
    return res;
}
Другие вопросы по тегам