diff --git a/viewer/JFJochHttpReader.cpp b/viewer/JFJochHttpReader.cpp index 6cc7f82f..7103ad5e 100644 --- a/viewer/JFJochHttpReader.cpp +++ b/viewer/JFJochHttpReader.cpp @@ -34,11 +34,21 @@ namespace { JFJochHttpReader::HttpResult JFJochHttpReader::Request(const std::string &method, const std::string &path, const std::string &body, const std::string &content_type) const { - EnsureCurlGlobalInit(); + // One easy handle is reused across requests so the connection stays alive. curl_easy_reset + // clears the previous request's options but deliberately keeps the live connection and the DNS + // / TLS-session caches, so a same-host request reuses the open socket; if that socket has since + // been closed, libcurl reconnects (and retries the request) on its own. + std::lock_guard cl(curl_mutex); - CURL *curl = curl_easy_init(); - if (curl == nullptr) - throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Could not initialize CURL"); + if (curl_handle == nullptr) { + EnsureCurlGlobalInit(); + curl_handle = curl_easy_init(); + if (curl_handle == nullptr) + throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Could not initialize CURL"); + } else { + curl_easy_reset(curl_handle); + } + CURL *curl = curl_handle; HttpResult result; const std::string url = addr + path; @@ -65,10 +75,21 @@ JFJochHttpReader::HttpResult JFJochHttpReader::Request(const std::string &method if (headers != nullptr) curl_slist_free_all(headers); - curl_easy_cleanup(curl); return result; } +JFJochHttpReader::~JFJochHttpReader() { + ResetConnection(); +} + +void JFJochHttpReader::ResetConnection() const { + std::lock_guard cl(curl_mutex); + if (curl_handle != nullptr) { + curl_easy_cleanup(curl_handle); + curl_handle = nullptr; + } +} + void JFJochHttpReader::Close() { std::unique_lock ul(http_mutex); addr = ""; @@ -77,6 +98,7 @@ void JFJochHttpReader::Close() { last_op_http_sync = false; cached_pixel_mask.reset(); cached_pixel_mask_arm_date.clear(); + ResetConnection(); // drop the kept-alive connection on disconnect } ImageBufferStatus JFJochHttpReader::GetImageBufferStatus() const { @@ -267,6 +289,7 @@ std::shared_ptr JFJochHttpReader::UpdateDataset_i() { void JFJochHttpReader::ReadURL(const std::string &url) { std::unique_lock ul(http_mutex); + ResetConnection(); // selecting an address (re)opens the connection from scratch addr = url; if (url.empty()) SetStartMessage({}); diff --git a/viewer/JFJochHttpReader.h b/viewer/JFJochHttpReader.h index dfaf27d0..3dbbd122 100644 --- a/viewer/JFJochHttpReader.h +++ b/viewer/JFJochHttpReader.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "../reader/JFJochReader.h" #include "../common/BrokerStatus.h" #include "../common/ImageBuffer.h" @@ -12,6 +14,15 @@ class JFJochHttpReader : public JFJochReader { mutable std::mutex http_mutex; std::string addr; + // Persistent libcurl handle: created lazily on the first request (i.e. as soon as an address + // is selected) and then reused for every subsequent request so the TCP/TLS connection is kept + // alive. libcurl transparently reopens the connection if it was dropped (idle timeout, + // server-side session limit, ...). curl_mutex serialises access because a single easy handle + // must never be used from two threads at once; it is a leaf lock (nothing else is taken while + // it is held), so it cannot deadlock with http_mutex. + mutable std::mutex curl_mutex; + mutable CURL *curl_handle = nullptr; + std::optional last_image_buffer_counter; bool last_op_http_sync = false; @@ -32,6 +43,10 @@ class JFJochHttpReader : public JFJochReader { HttpResult Request(const std::string &method, const std::string &path, const std::string &body = {}, const std::string &content_type = {}) const; + // Tear down the persistent connection (on disconnect, when a new address is selected, and in + // the destructor); the next Request transparently re-opens it. + void ResetConnection() const; + ImageBufferStatus GetImageBufferStatus() const; bool LoadImage_i(std::shared_ptr &dataset, @@ -42,7 +57,7 @@ class JFJochHttpReader : public JFJochReader { std::shared_ptr UpdateDataset_i(); std::vector GetPlot_i(const std::string &plot_type, float fill_value = 0.0) const; public: - ~JFJochHttpReader() override = default; + ~JFJochHttpReader() override; void ReadURL(const std::string& url); uint64_t GetNumberOfImages() const override;