Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use the winhttp api with "transfer-encoding: chunked"

I'm trying to send some data to a web service which requires the "Transfer-encoding: chunked" header. It works fine with a normal POST request. But as soon as I add the header, I always get:

The content could not be delivered due to the following condition: Received invalid request from client

This is the part where the request is sent:

std::vector<std::wstring> m_headers;
m_headers.push_back(TEXT("Transfer-encoding: chunked"));
std::wstring m_verb(TEXT("POST"));
std::vector<unsigned __int8> m_payload;

HINTERNET m_connectionHandle = WinHttpConnect(m_http->getSessionHandle(), hostName.c_str(), m_urlParts.nPort, 0);
if (!m_connectionHandle) {
    std::cout << "InternetConnect failed: " << GetLastError() << std::endl;
    return;
}

__int32 requestFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_REFRESH;
HINTERNET m_requestHandle = WinHttpOpenRequest(m_connectionHandle, m_verb.c_str(), (path + extra).c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, requestFlags);
if(!m_requestHandle) {
    std::cout << "HttpOpenRequest failed: " << GetLastError() << std::endl;
    return;
}

for(auto header : m_headers) {
    if(!WinHttpAddRequestHeaders(m_requestHandle, (header + TEXT("\r\n")).c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD)) {
        std::cout << "WinHttpAddRequestHeaders failed: " << GetLastError() << std::endl;
        return;
    }
}

if(!WinHttpSendRequest(m_requestHandle, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, (DWORD_PTR)this)) {
    std::cout << "HttpSendRequest failed: " << GetLastError() << std::endl;
    return;
}

unsigned chunkSize = 1024;
unsigned chunkCount = m_payload.size() / chunkSize;
char chunksizeString[128];
for (unsigned i = 0; i <= chunkCount; i++) {
    unsigned actualChunkSize = std::min<unsigned>(chunkSize, m_payload.size() - i * chunkSize);
    sprintf_s(chunksizeString, "%d\r\n", actualChunkSize);
    if (!WinHttpWriteData(m_requestHandle, chunksizeString, strlen(chunksizeString), (LPDWORD)&m_totalBytesWritten)) {
        std::cout << "HttpWriteData failed: " << GetLastError() << std::endl;
        return;
    }
    if (!WinHttpWriteData(m_requestHandle, m_payload.data() + i * chunkSize, actualChunkSize, (LPDWORD)&m_totalBytesWritten)) {
        std::cout << "HttpWriteData failed: " << GetLastError() << std::endl;
        return;
    }
}

// terminate chunked transfer
if (!WinHttpWriteData(m_requestHandle, "0\r\n", strlen("0\r\n"), (LPDWORD)&m_totalBytesWritten)) {
    std::cout << "HttpWriteData failed: " << GetLastError() << std::endl;
    return;
}

if(!WinHttpReceiveResponse(m_requestHandle, NULL)) {
    std::wcout << "HttpReceiveResponse failed: " << GetLastError() << std::endl;
    return;
}

I had to copy it from different files, so I hope I got all the important variable definitions. Right now I only use it synchronously since I thought it easier to debug.

As it works with normal POST requests (where I just use WinHttpSendRequest with the payload) I'm guessing it must have to do with the way I use WinHttpSendRequest & WinHttpWriteData, I just don't see how else it should be used.

Any help is appreciated!

like image 423
sph Avatar asked Oct 23 '17 15:10

sph


People also ask

How do I enable chunked transfer encoding?

To enable chunked transfer encoding, set the value for AspEnableChunkedEncoding to True in the metabase for the site, the server, or the virtual directory that you want to enable chunked transfer encoding for. By default, the value is True, and it is set at the Web service level.

How does transfer encoding chunked work?

In chunked transfer encoding, the data stream is divided into a series of non-overlapping "chunks". The chunks are sent out and received independently of one another. No knowledge of the data stream outside the currently-being-processed chunk is necessary for both the sender and the receiver at any given time.

What is API chunking?

A chunked response means that instead of waiting for the entire result, split the result into chunks (partial results) and send one after the other. Sending a response in chunks is useful for a RESTful web API if the resource returned by the API is huge in size.


2 Answers

You need to split data into chunks manually like this:

int chunkSize = 512;       // can be anything
char chunkSizeString[128]; // large enough string buffer
for (int i=0; i<chunksCount; ++i) {
    int actualChunkSize = chunkSize; // may be less when passing the last chunk of data (if that's not a multiple of chunkSize)
    sprintf(chunkSizeString, "%d\r\n", actualChunkSize);
    WinHttpWriteData(m_requestHandle, chunkSizeString, strlen(chunkSizeString), (LPDWORD)&m_totalBytesWritten);
    WinHttpWriteData(m_requestHandle, m_payload.data() + i*chunkSize, actualChunkSize, (LPDWORD)&m_totalBytesWritten);
}
WinHttpWriteData(m_requestHandle, "0\r\n", strlen("0\r\n"), (LPDWORD)&m_totalBytesWritten); // the last zero chunk, end of transmission 
like image 112
Anton Malyshev Avatar answered Nov 04 '22 02:11

Anton Malyshev


Thanks to the link provided by @anton-malyshev I was able to find the solution, I just replaced all calls to WinHttpWriteData above with this:

/* Chunk header */
char chunksizeString[64];
sprintf_s(chunksizeString, "%X\r\n", m_payload.size());
if (!WinHttpWriteData(m_requestHandle, chunksizeString, strlen(chunksizeString), (LPDWORD)&m_totalBytesWritten)) {
    std::wcout << "WinHttpWriteData chunk header failed: " << getHttpErrorMessage(GetLastError()) << std::endl;
    return;
}

/* Chunk body */
if (!WinHttpWriteData(m_requestHandle, m_payload.data(), m_payload.size(), (LPDWORD)&m_totalBytesWritten)) {
    std::wcout << "WinHttpWriteData chunk body failed: " << getHttpErrorMessage(GetLastError()) << std::endl;
    return;
}

/* Chunk footer */
if (!WinHttpWriteData(m_requestHandle, "\r\n", 2, (LPDWORD)&m_totalBytesWritten)) {
    std::wcout << "WinHttpWriteDatachunk footer failed: " << getHttpErrorMessage(GetLastError()) << std::endl;
    return;
}

/* Terminate chunk transfer */
if (!WinHttpWriteData(m_requestHandle, "0\r\n\r\n", 5, (LPDWORD)&m_totalBytesWritten)) {
    std::wcout << "WinHttpWriteData chunk termination failed: " << getHttpErrorMessage(GetLastError()) << std::endl;
    return;
}
like image 30
sph Avatar answered Nov 04 '22 04:11

sph