如何使用带有“传输编码:分块”的 winhttp api

Posted

技术标签:

【中文标题】如何使用带有“传输编码:分块”的 winhttp api【英文标题】:How do I use the winhttp api with "transfer-encoding: chunked" 【发布时间】:2018-04-04 05:55:09 【问题描述】:

我正在尝试将一些数据发送到需要“Transfer-encoding: chunked”标头的 Web 服务。它适用于正常的 POST 请求。 但只要我添加标题,我总是得到:

由于以下情况,无法交付内容: 收到来自客户端的无效请求

这是发送请求的部分:

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;

我不得不从不同的文件中复制它,所以我希望我得到了所有重要的变量定义。现在我只同步使用它,因为我认为它更容易调试。

由于它适用于普通的 POST 请求(我只是将 WinHttpSendRequest 与有效负载一起使用),我猜它一定与我使用 WinHttpSendRequest 和 WinHttpWriteData 的方式有关,我只是不知道它应该如何使用.

感谢任何帮助!

【问题讨论】:

vector 传递给WinHttpWriteData() 时,您将错误的指针传递给lpBuffer 参数。您需要将&amp;m_payload 更改为&amp;m_payload[0]m_payload.data() 不幸的是,这并没有改变任何东西。 【参考方案1】:

您需要像这样手动将数据拆分成块:

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 

【讨论】:

所以我必须在单独的 WriteData 调用中预先告诉 winhttp 我作为字符串发送的实际大小,而有一个大小参数?这对我来说似乎很奇怪(不是说我怀疑你的建议,我只是不明白为什么)。无论如何,它并没有改变我的问题,仍然得到“无效请求”响应。我是 http 的新手,有没有办法在发送之前只显示实际请求? 显示你修改的代码,可能还有一些错误 我在上面编辑了它。现在它实际上大部分时间在一个带有 ERROR_WINHTTP_CONNECTION_ERROR 的 WriteData 调用中都失败了。当它通过时,虽然我得到与以前相同的 html 响应。 这里有一个错误:chunkCount = m_payload.size() / chunkSize; - 如果不是chunkSize 的倍数,m_payload.size() 是错误的(需要 +1);还要检查 m_totalBytesWritten 是否等于传递的数据大小。 你的权利,对于语义它可能应该说 +1,但我改为迭代直到 【参考方案2】:

感谢@anton-malyshev 提供的链接,我能够找到解决方案,我只是将上面对 WinHttpWriteData 的所有调用替换为:

/* 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;

【讨论】:

以上是关于如何使用带有“传输编码:分块”的 winhttp api的主要内容,如果未能解决你的问题,请参考以下文章

如何使用多部分/表单和分块编码在 spring mvc 中接收文件上传?

标头传输编码:分块并请求为空

未收到 WinHttp POST 正文

任何人都知道为啥这个带有 WinHttp 的 POST 失败了

WinHTTP 错误 URL 无效

WinHttp Delphi 包装器