Reset the client's TLS error per attempt and read it under the request lock

Reset last_ssl_error_ and last_backend_error_ at the start of each
attempt in send_() rather than once in send(), so a request that
succeeds on the SSLPeerCouldBeClosed_ retry does not report the TLS
error of the attempt before it.

Now that every request writes those members, hold request_mutex_ until
the Result has read them, so another thread's request on the same
client cannot reset or replace them in between.

Also pick the TLS error up in a lambda in SSLClient::process_socket()
instead of through an out-parameter on process_client_socket_ssl(),
which has no other caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGAYpQgXt95LdDQcJYgjB8
This commit is contained in:
Claude committed 2026-10-17 02:40:23 +00:00
1 parent 28741cb9d1
commit a1d4ccedea
1 file changed
+32 -25
+32 -25
View File
@@ -10725,14 +10725,11 @@ inline bool process_client_socket_ssl(
tls::session_t session, socket_t sock, time_t read_timeout_sec,
time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec, time_t max_timeout_msec,
std::chrono::time_point<std::chrono::steady_clock> start_time, T callback,
tls::TlsError *tls_error = nullptr) {
std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
write_timeout_sec, write_timeout_usec, max_timeout_msec,
start_time);
auto ret = callback(strm);
if (tls_error) { *tls_error = strm.tls_error(); }
return ret;
return callback(strm);
}
inline std::pair<std::string, std::string> make_digest_authentication_header(
@@ -15224,14 +15221,6 @@ inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
#ifdef CPPHTTPLIB_SSL_ENABLED
// Don't let an earlier request's TLS error show up in this one's Result. A
// client that failed to set up its TLS context keeps reporting why.
if (is_valid()) {
last_ssl_error_ = 0;
last_backend_error_ = 0;
}
#endif
auto ret = send_(req, res, error);
if (error == Error::SSLPeerCouldBeClosed_) {
assert(!ret);
@@ -15243,6 +15232,15 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
}
inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
#ifdef CPPHTTPLIB_SSL_ENABLED
// Don't let an earlier attempt's TLS error show up in this one's Result. A
// client that failed to set up its TLS context keeps reporting why.
if (is_valid()) {
last_ssl_error_ = 0;
last_backend_error_ = 0;
}
#endif
{
std::lock_guard<std::mutex> guard(socket_mutex_);
@@ -15341,6 +15339,10 @@ inline Result ClientImpl::send(const Request &req) {
inline Result ClientImpl::send_(Request &&req) {
auto res = detail::make_unique<Response>();
auto error = Error::Success;
#ifdef CPPHTTPLIB_SSL_ENABLED
// Hold the request lock until the Result has read this request's TLS error
std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
#endif
auto ret = send(req, *res, error);
#ifdef CPPHTTPLIB_SSL_ENABLED
return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers),
@@ -16413,6 +16415,10 @@ inline Result ClientImpl::send_with_content_provider_and_receiver(
auto error = Error::Success;
#ifdef CPPHTTPLIB_SSL_ENABLED
// Hold the request lock until the Result has read this request's TLS error
std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
#endif
auto res = send_with_content_provider_and_receiver(
req, body, content_length, std::move(content_provider),
std::move(content_provider_without_length), content_type,
@@ -18578,20 +18584,21 @@ inline bool SSLClient::process_socket(
std::chrono::time_point<std::chrono::steady_clock> start_time,
std::function<bool(Stream &strm)> callback) {
assert(socket.ssl);
tls::TlsError tls_error;
auto ret = detail::process_client_socket_ssl(
return detail::process_client_socket_ssl(
socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time,
std::move(callback), &tls_error);
// A request that failed after the handshake reports Error::Read or
// Error::Write; keep the TLS error behind it, such as a fatal alert from
// the server, for Result::ssl_error() and ssl_backend_error()
if (!ret && tls_error.code != tls::ErrorCode::Success) {
last_ssl_error_ = static_cast<int>(tls_error.code);
last_backend_error_ = tls_error.backend_code;
}
return ret;
[&](detail::SSLSocketStream &strm) {
auto ret = callback(strm);
// A request that failed after the handshake reports Error::Read or
// Error::Write; keep the TLS error behind it, such as a fatal alert
// from the server, for Result::ssl_error() and ssl_backend_error()
const auto &tls_error = strm.tls_error();
if (!ret && tls_error.code != tls::ErrorCode::Success) {
last_ssl_error_ = static_cast<int>(tls_error.code);
last_backend_error_ = tls_error.backend_code;
}
return ret;
});
}
inline bool SSLClient::is_ssl() const { return true; }