-
Notifications
You must be signed in to change notification settings - Fork 1k
Open
Description
Description
When reusing a Session object for multiple requests with different HTTP methods,
the PrepareXxx() methods fail to clear all previous method-related curl options.
Specifically:
- CURLOPT_POST flag (set implicitly by CURLOPT_POSTFIELDS) persists across requests
- PrepareDelete() only sets CURLOPT_HTTPGET=0, doesn't clear CURLOPT_POST=1
- PrepareGet() only resets state for standard GET (without body)
This causes hangs/errors in sequences like:
- POST request -> 2. DELETE request (DELETE inherits POST state)
- POST request -> 2. GET with body (GET inherits POST state)
Example/How to Reproduce
I write code like this in my project, the line curl_easy_setopt(session_->GetCurlHolder()->handle, CURLOPT_HTTPGET, 1L); can not be removed, once removed this line this will cause a stuck.
DropTable Test (Working correctly)
- CreateTable → POST /v1/.../.../tables
- TableExists → HEAD /v1/.../.../tables/table_to_drop ← Note: This intermediate request exists!
- DropTable → DELETE /v1/.../.../tables/table_to_drop
RegisterTable Test (Stuck)
- CreateTable → POST /v1/.../.../tables
- DropTable → DELETE /v1/.../.../tables/t1 ← Note: This follows immediately after the POST request!
void HttpClient::PrepareSession(
const std::string& path, const HttpMethod& method, const std::unordered_map<std::string, std::string>& params,
const std::unordered_map<std::string, std::string>& headers) {
session_->SetUrl(cpr::Url{path});
session_->SetParameters(GetParameters(params));
session_->RemoveContent();
// clear lingering POST mode state from prior requests. CURLOPT_POST is implicitly set
// to 1 by POST requests, and this state is not reset by RemoveContent(), so we must
// manually enforce HTTP GET to clear it.
curl_easy_setopt(session_->GetCurlHolder()->handle, CURLOPT_HTTPGET, 1L);
switch (method) {
case HttpMethod::kGet:
session_->PrepareGet();
break;
case HttpMethod::kPost:
session_->PreparePost();
break;
case HttpMethod::kPut:
session_->PreparePut();
break;
case HttpMethod::kDelete:
session_->PrepareDelete();
break;
case HttpMethod::kHead:
session_->PrepareHead();
break;
}
auto final_headers = MergeHeaders(default_headers_, headers);
session_->SetHeader(final_headers);
}
Result<HttpResponse> HttpClient::Get(
const std::string& path, const std::unordered_map<std::string, std::string>& params,
const std::unordered_map<std::string, std::string>& headers,
const ErrorHandler& error_handler) {
cpr::Response response;
{
std::lock_guard guard(session_mutex_);
PrepareSession(path, HttpMethod::kGet, params, headers);
response = session_->Get();
}
RETURN_UNEXPECTED(HandleFailureResponse(response, error_handler));
HttpResponse http_response;
http_response.impl_ = std::make_unique<HttpResponse::Impl>(std::move(response));
return http_response;
}
Result<HttpResponse> HttpClient::Post(
const std::string& path, const std::string& body,
const std::unordered_map<std::string, std::string>& headers,
const ErrorHandler& error_handler) {
cpr::Response response;
{
std::lock_guard guard(session_mutex_);
PrepareSession(path, HttpMethod::kPost, /*params=*/{}, headers);
session_->SetBody(cpr::Body{body});
response = session_->Post();
}
RETURN_UNEXPECTED(HandleFailureResponse(response, error_handler));
HttpResponse http_response;
http_response.impl_ = std::make_unique<HttpResponse::Impl>(std::move(response));
return http_response;
}
Possible Fix
void Session::PrepareDelete() {
curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 1L); // add this
curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "DELETE");
prepareCommon();
}
void Session::PreparePost() {
curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 1L); // add this
curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
// ... others
}
Where did you get it from?
GitHub (branch e.g. master)
Additional Context/Your Environment
- OS: MacOS 15.6
- Version: Apple clang version 17.0.0 (clang-1700.0.13.5)
Target: arm64-apple-darwin24.6.0
Thread model: posix