Skip to content

Session::PrepareXxx() methods don't fully reset HTTP method state, causing conflicts when reusing sessions #1284

@HeartLinked

Description

@HeartLinked

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:

  1. POST request -> 2. DELETE request (DELETE inherits POST state)
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions