回调中的 WinHTTP 异步上下文无效
WinHTTP async context invalid in callback
我正在尝试使用 WinHTTP 对服务器进行异步调用,但遇到问题:
- 在转换和访问请求 ID 等结构值时,回调中的上下文似乎不正确。
-
WinHttpReceiveResponse
总是 returns 使用 GetLastError()
的错误代码 6
这是无效句柄的系统错误,我怀疑它与第 1 点有关。
我能想到的上下文不再有效的唯一原因是它是否超出了范围,但我看不出这是怎么发生的。任何见解将不胜感激。
我的上下文结构是
struct connection_context
{
int request_id;
HANDLE read_complete;
DWORD timeout;
HINTERNET request_handle;
std::string request_payload;
std::string read_buffer;
std::string response_payload;
};
我的 WinHTTP 回调是
void async_callback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)
{
if (dwContext != 0)
{
connection_context* ctx = (connection_context*)dwContext;
switch(dwInternetStatus)
{
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
if(!WinHttpReceiveResponse(ctx->request_handle, NULL))
{
printf("Request %d Error in receive response, code: %d\n", ctx->request_id, GetLastError());
SetEvent(ctx->read_complete);
}
break;
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
{
DWORD statusCode = 0;
DWORD statusCodeSize = sizeof(DWORD);
if (!WinHttpQueryHeaders(ctx->request_handle, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX))
{
printf("Request %d failed to query headers\n", ctx->request_id);
SetEvent(ctx->read_complete);
}
if (HTTP_STATUS_OK != statusCode)
{
printf("Request %d bad status\n", ctx->request_id);
SetEvent(ctx->read_complete);
}
if(!WinHttpQueryDataAvailable(ctx->request_handle, NULL))
{
printf("Reques %d bad query data available\n", ctx->request_id);
SetEvent(ctx->read_complete);
}
break;
}
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
if (*((LPDWORD)lpvStatusInformation) != 0)
{
DWORD buffer_size = *((LPDWORD)lpvStatusInformation) + 1;
ctx->read_buffer = std::string(buffer_size, '[=11=]');
if (!WinHttpReadData(ctx->request_handle, &ctx->read_buffer[0], ctx->read_buffer.size(), 0))
{
printf("Request %d bad subsequent read data\n", ctx->request_id);
SetEvent(ctx->read_complete);
}
}
else
{
printf("Request %d read complete with size %zd\n", ctx->request_id, ctx->response_payload.size());
SetEvent(ctx->read_complete);
}
break;
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
if (dwStatusInformationLength != 0)
{
ctx->response_payload.append(ctx->read_buffer.c_str(), dwStatusInformationLength);
ctx->read_buffer.clear();
if(!WinHttpQueryDataAvailable(ctx->request_handle, NULL))
{
printf("Request %d bad query data available\n", ctx->request_id);
SetEvent(ctx->read_complete);
}
}
break;
}
}
}
建立连接、发送请求并等待它们完成的函数是
void fetch_status(std::string login_result, LPCWSTR pswzServerName, WORD port, LPCWSTR lpszVersion, LPCWSTR lpszReferrer, LPCWSTR* lplpszAcceptTypes, DWORD dwFlags, std::unordered_map<int, std::string>* results)
{
HINTERNET session = WinHttpOpen(L"Steam", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC);
if (!session)
{
printf("Failed to make session\n");
return;
}
WinHttpSetStatusCallback(session, async_callback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
HINTERNET connect = WinHttpConnect(session, pswzServerName, port, 0);
if (!connect)
{
printf("Failed to make connect\n");
WinHttpCloseHandle(session);
return;
}
std::vector<connection_context> contexts;
std::string token = login_result.substr(60, 19) + login_result.substr(2, 14);
token = to_hex(token);
for (auto i = 0; i < ggs_get_aobs.size(); i++)
{
connection_context ctx;
contexts.push_back(ctx);
contexts.back().request_id = i;
contexts.back().timeout = 5000; // 5 seconds
contexts.back().request_payload = generate_stat_get_request(token, ggs_get_aobs[i]);
contexts.back().request_handle = WinHttpOpenRequest(connect, L"POST", L"/api/statistics/get", NULL, WINHTTP_NO_REFERER, lplpszAcceptTypes, WINHTTP_FLAG_SECURE);
if (!contexts.back().request_handle)
{
printf("Failed to open request\n");
continue;
}
int headers_added = 0;
if(WinHttpAddRequestHeaders(contexts.back().request_handle, L"Connection: keep-alive\r\n", -1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE))
{
headers_added++;
}
if(WinHttpAddRequestHeaders(contexts.back().request_handle, L"Content-Type: application/x-www-form-urlencoded\r\n", -1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE))
{
headers_added++;
}
if(WinHttpAddRequestHeaders(contexts.back().request_handle, L"Cache-Control: no-cache\r\n", -1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE))
{
headers_added++;
}
if(WinHttpAddRequestHeaders(contexts.back().request_handle, L"Cookie: theme=theme-dark\r\n", -1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE))
{
headers_added++;
}
if (WinHttpAddRequestHeaders(contexts.back().request_handle, L"User-Agent: Steam\r\n", -1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE))
{
headers_added++;
}
if (headers_added < 5)
{
printf("Failed to add headers\n");
continue;
}
if(!WinHttpSendRequest(contexts.back().request_handle, WINHTTP_NO_ADDITIONAL_HEADERS, -1L,
&contexts.back().request_payload[0], contexts.back().request_payload.size() + 1, contexts.back().request_payload.size() + 1, (DWORD_PTR)&contexts.back()))
{
printf("Failed to send request\n");
continue;
}
printf("Sent async http request %d, otional(size:%zd)\n", i, contexts.back().request_payload.size() + 1);
}
for (auto i = 0; i < contexts.size(); i++)
{
WaitForSingleObject(contexts[i].read_complete, contexts[i].timeout);
WinHttpCloseHandle(contexts[i].request_handle);
}
WinHttpCloseHandle(connect);
WinHttpCloseHandle(session);
}
在调用 WinHttpSendRequest()
的循环的每次迭代中,设置每个请求的上下文,对 contexts.push_back()
的调用有可能重新分配向量的内部数组,从而使之前的所有 connection_context*
您在之前的请求中使用的指针。
为避免重新分配,您需要在进入循环之前预先调整数组的大小。
要么像这样:
std::vector<connection_context> contexts(ggs_get_aobs.size());
...
size_t i = 0;
for (auto &ctx : contexts)
{
ctx.request_id = i;
...
ctx.request_handle = WinHttpOpenRequest(...);
...
WinHttpSendRequest(ctx.request_handle, ..., reinterpret_cast<DWORD_PTR>(&ctx))
...
++i;
}
或者,像这样:
std::vector<connection_context> contexts;
contexts.reserve(ggs_get_aobs.size());
...
for (auto i = 0; i < ggs_get_aobs.size(); ++i)
{
contexts.emplace_back();
auto &ctx = contexts.back();
/* or, in C++17 and later...
auto &ctx = contexts.emplace_back();
*/
ctx.request_id = i;
...
ctx.request_handle = WinHttpOpenRequest(...);
...
WinHttpSendRequest(ctx.request_handle, ..., reinterpret_cast<DWORD_PTR>(&ctx))
...
}
我正在尝试使用 WinHTTP 对服务器进行异步调用,但遇到问题:
- 在转换和访问请求 ID 等结构值时,回调中的上下文似乎不正确。
-
WinHttpReceiveResponse
总是 returns 使用GetLastError()
的错误代码6
这是无效句柄的系统错误,我怀疑它与第 1 点有关。
我能想到的上下文不再有效的唯一原因是它是否超出了范围,但我看不出这是怎么发生的。任何见解将不胜感激。
我的上下文结构是
struct connection_context
{
int request_id;
HANDLE read_complete;
DWORD timeout;
HINTERNET request_handle;
std::string request_payload;
std::string read_buffer;
std::string response_payload;
};
我的 WinHTTP 回调是
void async_callback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)
{
if (dwContext != 0)
{
connection_context* ctx = (connection_context*)dwContext;
switch(dwInternetStatus)
{
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
if(!WinHttpReceiveResponse(ctx->request_handle, NULL))
{
printf("Request %d Error in receive response, code: %d\n", ctx->request_id, GetLastError());
SetEvent(ctx->read_complete);
}
break;
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
{
DWORD statusCode = 0;
DWORD statusCodeSize = sizeof(DWORD);
if (!WinHttpQueryHeaders(ctx->request_handle, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX))
{
printf("Request %d failed to query headers\n", ctx->request_id);
SetEvent(ctx->read_complete);
}
if (HTTP_STATUS_OK != statusCode)
{
printf("Request %d bad status\n", ctx->request_id);
SetEvent(ctx->read_complete);
}
if(!WinHttpQueryDataAvailable(ctx->request_handle, NULL))
{
printf("Reques %d bad query data available\n", ctx->request_id);
SetEvent(ctx->read_complete);
}
break;
}
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
if (*((LPDWORD)lpvStatusInformation) != 0)
{
DWORD buffer_size = *((LPDWORD)lpvStatusInformation) + 1;
ctx->read_buffer = std::string(buffer_size, '[=11=]');
if (!WinHttpReadData(ctx->request_handle, &ctx->read_buffer[0], ctx->read_buffer.size(), 0))
{
printf("Request %d bad subsequent read data\n", ctx->request_id);
SetEvent(ctx->read_complete);
}
}
else
{
printf("Request %d read complete with size %zd\n", ctx->request_id, ctx->response_payload.size());
SetEvent(ctx->read_complete);
}
break;
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
if (dwStatusInformationLength != 0)
{
ctx->response_payload.append(ctx->read_buffer.c_str(), dwStatusInformationLength);
ctx->read_buffer.clear();
if(!WinHttpQueryDataAvailable(ctx->request_handle, NULL))
{
printf("Request %d bad query data available\n", ctx->request_id);
SetEvent(ctx->read_complete);
}
}
break;
}
}
}
建立连接、发送请求并等待它们完成的函数是
void fetch_status(std::string login_result, LPCWSTR pswzServerName, WORD port, LPCWSTR lpszVersion, LPCWSTR lpszReferrer, LPCWSTR* lplpszAcceptTypes, DWORD dwFlags, std::unordered_map<int, std::string>* results)
{
HINTERNET session = WinHttpOpen(L"Steam", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC);
if (!session)
{
printf("Failed to make session\n");
return;
}
WinHttpSetStatusCallback(session, async_callback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
HINTERNET connect = WinHttpConnect(session, pswzServerName, port, 0);
if (!connect)
{
printf("Failed to make connect\n");
WinHttpCloseHandle(session);
return;
}
std::vector<connection_context> contexts;
std::string token = login_result.substr(60, 19) + login_result.substr(2, 14);
token = to_hex(token);
for (auto i = 0; i < ggs_get_aobs.size(); i++)
{
connection_context ctx;
contexts.push_back(ctx);
contexts.back().request_id = i;
contexts.back().timeout = 5000; // 5 seconds
contexts.back().request_payload = generate_stat_get_request(token, ggs_get_aobs[i]);
contexts.back().request_handle = WinHttpOpenRequest(connect, L"POST", L"/api/statistics/get", NULL, WINHTTP_NO_REFERER, lplpszAcceptTypes, WINHTTP_FLAG_SECURE);
if (!contexts.back().request_handle)
{
printf("Failed to open request\n");
continue;
}
int headers_added = 0;
if(WinHttpAddRequestHeaders(contexts.back().request_handle, L"Connection: keep-alive\r\n", -1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE))
{
headers_added++;
}
if(WinHttpAddRequestHeaders(contexts.back().request_handle, L"Content-Type: application/x-www-form-urlencoded\r\n", -1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE))
{
headers_added++;
}
if(WinHttpAddRequestHeaders(contexts.back().request_handle, L"Cache-Control: no-cache\r\n", -1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE))
{
headers_added++;
}
if(WinHttpAddRequestHeaders(contexts.back().request_handle, L"Cookie: theme=theme-dark\r\n", -1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE))
{
headers_added++;
}
if (WinHttpAddRequestHeaders(contexts.back().request_handle, L"User-Agent: Steam\r\n", -1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE))
{
headers_added++;
}
if (headers_added < 5)
{
printf("Failed to add headers\n");
continue;
}
if(!WinHttpSendRequest(contexts.back().request_handle, WINHTTP_NO_ADDITIONAL_HEADERS, -1L,
&contexts.back().request_payload[0], contexts.back().request_payload.size() + 1, contexts.back().request_payload.size() + 1, (DWORD_PTR)&contexts.back()))
{
printf("Failed to send request\n");
continue;
}
printf("Sent async http request %d, otional(size:%zd)\n", i, contexts.back().request_payload.size() + 1);
}
for (auto i = 0; i < contexts.size(); i++)
{
WaitForSingleObject(contexts[i].read_complete, contexts[i].timeout);
WinHttpCloseHandle(contexts[i].request_handle);
}
WinHttpCloseHandle(connect);
WinHttpCloseHandle(session);
}
在调用 WinHttpSendRequest()
的循环的每次迭代中,设置每个请求的上下文,对 contexts.push_back()
的调用有可能重新分配向量的内部数组,从而使之前的所有 connection_context*
您在之前的请求中使用的指针。
为避免重新分配,您需要在进入循环之前预先调整数组的大小。
要么像这样:
std::vector<connection_context> contexts(ggs_get_aobs.size());
...
size_t i = 0;
for (auto &ctx : contexts)
{
ctx.request_id = i;
...
ctx.request_handle = WinHttpOpenRequest(...);
...
WinHttpSendRequest(ctx.request_handle, ..., reinterpret_cast<DWORD_PTR>(&ctx))
...
++i;
}
或者,像这样:
std::vector<connection_context> contexts;
contexts.reserve(ggs_get_aobs.size());
...
for (auto i = 0; i < ggs_get_aobs.size(); ++i)
{
contexts.emplace_back();
auto &ctx = contexts.back();
/* or, in C++17 and later...
auto &ctx = contexts.emplace_back();
*/
ctx.request_id = i;
...
ctx.request_handle = WinHttpOpenRequest(...);
...
WinHttpSendRequest(ctx.request_handle, ..., reinterpret_cast<DWORD_PTR>(&ctx))
...
}