Files
Jungfraujoch/viewer/JFJochHttpReader.h
T
leonarski_fandClaude Opus 5.5 89207fe2bd viewer: cpp-httplib + Mbed TLS instead of libcurl for the broker client
The viewer's live-broker client goes back to cpp-httplib, which the broker
already fetches; libcurl was only there for Kerberos, which is no longer
wanted. HTTPS comes from Mbed TLS 4.2.0, fetched and statically linked for
viewer builds only, so one TLS backend now serves Linux, Windows and macOS
(the macOS viewer previously had no TLS at all, and Linux needed host
OpenSSL >= 3.0). cpp-httplib verifies servers against the OS trust store:
the Windows certificate store, the macOS Keychain, the CA bundle on Linux.

CPPHTTPLIB_MBEDTLS_SUPPORT is set on jfjoch_viewer only, not on
httplib::httplib, so the broker stays plain HTTP. JFJochHttpReader.cpp is
the only viewer source that includes httplib.h, and the header keeps
httplib::Client opaque so <windows.h> stays out of the other viewer units.

Checked on Linux: a viewer-only build links Mbed TLS statically, with no
curl and no system libssl; a standalone client built against the same
httplib + Mbed TLS fetches from real HTTPS hosts and rejects expired,
self-signed and wrong-host certificates. Windows and macOS are untested.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 21:16:04 +02:00

106 lines
4.8 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include "../reader/JFJochReader.h"
#include "../common/BrokerStatus.h"
#include "../common/ImageBuffer.h"
#include "../common/ROIDefinition.h"
#include <stdexcept>
// cpp-httplib's client, kept opaque here so this widely-included header does NOT pull in
// <httplib.h>. On Windows httplib.h drags in <windows.h>, whose min/max/ERROR/LoadImage macros
// would then clobber every viewer translation unit that includes this header (breaking std::min /
// std::max in gemmi, the `ERROR` enum, the LoadImage() calls, ...). <httplib.h> is included only in
// JFJochHttpReader.cpp.
namespace httplib { class Client; }
// The broker refused the request with 401/403: the current dataset is protected by tokens and the
// one we hold (or its absence) does not open it. Distinct from "broker unreachable" so the viewer
// can report it quietly instead of raising an error dialog.
struct HttpUnauthorized : public std::runtime_error {
using std::runtime_error::runtime_error;
};
class JFJochHttpReader : public JFJochReader {
mutable std::mutex http_mutex;
std::string addr;
// Sent as "Authorization: Bearer <token>" with every request while non-empty.
std::string bearer_token;
// Persistent HTTP(S) client: 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. httplib transparently reopens the connection if it was dropped (idle timeout,
// server-side session limit, ...). client_mutex serialises access because one client 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 client_mutex;
mutable std::unique_ptr<httplib::Client> client;
// Path part of addr (e.g. "/jfjoch" behind a reverse proxy), prepended to every request path;
// the client itself is constructed from the scheme://host:port part only.
mutable std::string path_prefix;
std::optional<int64_t> last_image_buffer_counter;
bool last_op_http_sync = false;
// Cache of the (constant-per-acquisition) pixel mask, keyed on arm date, so the per-refresh
// dataset rebuild reuses one shared mask instead of reconstructing it every tick.
std::shared_ptr<const PixelMask> cached_pixel_mask;
std::string cached_pixel_mask_arm_date;
// Minimal HTTP result: whether a response arrived (transport-level success), the HTTP status
// code, and the (possibly binary) response body.
struct HttpResult {
bool ok = false;
long status = 0;
std::string body;
};
// Single HTTP request helper behind every endpoint call. method is "GET" or "PUT"; for PUT
// the body is sent with the given content_type.
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<JFJochReaderDataset> &dataset,
DataMessage& message,
std::vector<uint8_t> &buffer,
int64_t image_number,
bool update_dataset) override;
std::shared_ptr<JFJochReaderDataset> UpdateDataset_i();
std::vector<float> GetPlot_i(const std::string &plot_type, float fill_value = 0.0) const;
public:
// Both defined in the .cpp, where httplib::Client is a complete type.
JFJochHttpReader();
~JFJochHttpReader() override;
void ReadURL(const std::string& url);
// The dataset token; kept across Close() so a reconnect to the same broker reuses it.
void Token(const std::string &token);
uint64_t GetNumberOfImages() const override;
void Close() override;
// Refresh dataset/plots if the image buffer changed since the last poll (returns nullptr if
// unchanged). Always writes the current number of images to num_images_out. Does not load an image.
std::shared_ptr<const JFJochReaderDataset> RefreshDatasetIfChanged(int64_t &num_images_out);
void UploadUserMask(const std::vector<uint32_t>& mask);
[[nodiscard]] ROIDefinition GetROIDefinitions() const; // GET /config/roi
void UploadROIDefinitions(const ROIDefinition &rois) const; // PUT /config/roi
BrokerStatus GetBrokerStatus() const;
bool ReadRawImage(int64_t image_number, JFJochReaderRawImage &image) override;
std::vector<SpotToSave> ReadSpots(int64_t image) const override;
};