XRootD
Loading...
Searching...
No Matches
XrdClHttpOptionsCache.hh
Go to the documentation of this file.
1/******************************************************************************/
2/* Copyright (C) 2025, Pelican Project, Morgridge Institute for Research */
3/* */
4/* This file is part of the XrdClHttp client plugin for XRootD. */
5/* */
6/* XRootD is free software: you can redistribute it and/or modify it under */
7/* the terms of the GNU Lesser General Public License as published by the */
8/* Free Software Foundation, either version 3 of the License, or (at your */
9/* option) any later version. */
10/* */
11/* XRootD is distributed in the hope that it will be useful, but WITHOUT */
12/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
13/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
14/* License for more details. */
15/* */
16/* The copyright holder's institutional names and contributor's names may not */
17/* be used to endorse or promote products derived from this software without */
18/* specific prior written permission of the institution or contributor. */
19/******************************************************************************/
20
21#ifndef _XRDCLHTTP__OPTIONSCACHE_HH__
22#define _XRDCLHTTP__OPTIONSCACHE_HH__
23
24#include <array>
25#include <atomic>
26#include <chrono>
27#include <condition_variable>
28#include <mutex>
29#include <shared_mutex>
30#include <string>
31#include <thread>
32#include <unordered_map>
33
34 namespace XrdClHttp {
35
36 // A cache holding the known HTTP verbs for a given endpoint.
37 class VerbsCache {
38 public:
39
40 // Enumeration bitmask of the HTTP verbs that we can test for
41 enum class HttpVerb {
42 kUnset = 0, // Indicates that we haven't yet probed for the HTTP verb support.
43 kUnknown = 1, // Indicates we probed for support but the result was indeterminate (not provided by the server, network error)
44 kPROPFIND = 2, // Server claims to support PROPFIND
45 };
46 class HttpVerbs {
47 public:
48 HttpVerbs() = default;
49 HttpVerbs(HttpVerb verb) : m_verbs(static_cast<unsigned>(verb)) {}
50 HttpVerbs &operator|=(HttpVerb verb) {m_verbs |= static_cast<unsigned>(verb); return *this;}
51 bool IsSet(HttpVerb verb) const
52 {
53 if (verb == HttpVerb::kUnset) {return !m_verbs;}
54 return static_cast<unsigned>(m_verbs) & static_cast<unsigned>(verb);
55 }
56 unsigned GetValue() const {return m_verbs;}
57 private:
58 unsigned m_verbs{0};
59 };
60
61 static const std::string GetVerbString(HttpVerb ctype) {
62 switch (ctype) {
64 return "(unset)";
66 return "(unknown)";
68 return "PROPFIND";
69 }
70 }
71
72 void Put(const std::string &url, const HttpVerbs &verbs, const std::chrono::steady_clock::time_point &now=std::chrono::steady_clock::now()) const {
73 std::string modified_url;
74 auto key = GetUrlKey(url, modified_url);
75
76 const std::unique_lock sentry(m_mutex);
77
78 auto isKnown = !verbs.IsSet(HttpVerb::kUnknown);
79 auto lifetime = isKnown ? g_expiry_duration : g_negative_expiry_duration;
80
81// C++20 can elide the allocation for the string_view
82#if __cplusplus >= 202002L
83 auto iter = m_verbs_map.find(key);
84#else
85 auto iter = m_verbs_map.find(std::string(key));
86#endif
87 if (iter == m_verbs_map.end()) {
88 m_verbs_map.emplace(key, VerbEntry{now + lifetime, verbs});
89 } else if (isKnown || iter->second.m_verbs.IsSet(HttpVerb::kUnknown)) {
90 // Previous entry didn't know the verbs, but now we do
91 iter->second = {now + lifetime, verbs};
92 }
93 }
94
95 HttpVerbs Get(const std::string &url, const std::chrono::steady_clock::time_point &now=std::chrono::steady_clock::now()) const {
96 std::string modified_url;
97 auto key = GetUrlKey(url, modified_url);
98
99 const std::shared_lock sentry(m_mutex);
100#if __cplusplus >= 202002L
101 auto iter = m_verbs_map.find(key);
102#else
103 auto iter = m_verbs_map.find(std::string(key));
104#endif
105 if (iter == m_verbs_map.end()) {
106 m_cache_miss++;
107 return HttpVerbs{};
108 }
109 if (iter->second.m_expiry < now) {
110 m_cache_miss++;
111 return HttpVerbs{};
112 }
113 m_cache_hit++;
114 return iter->second.m_verbs;
115 }
116
117 // Get the cache key for a given URL
118 //
119 // Cache key should consist of the schema, host, and port portion of the URL.
120 static std::string_view GetUrlKey(const std::string &url, std::string &modified_url) {
121 auto authority_loc = url.find("://");
122 if (authority_loc == std::string::npos) {
123 return std::string_view();
124 }
125 auto path_loc = url.find('/', authority_loc + 3);
126 if (path_loc == std::string::npos) {
127 path_loc = url.length();
128 }
129
130 std::string_view url_view{url};
131 auto host_loc = url_view.substr(authority_loc + 3, path_loc - authority_loc - 3).find('@');
132 if (host_loc == std::string::npos) {
133 return url_view.substr(0, path_loc);
134 }
135 host_loc += authority_loc + 3;
136 modified_url = url.substr(0, authority_loc + 3) + std::string(url_view.substr(host_loc + 1, path_loc - host_loc - 1));
137 return modified_url;
138 }
139
140 uint64_t GetCacheHits() const {return m_cache_hit;}
141 uint64_t GetCacheMisses() const {return m_cache_miss;}
142
143 // Expire all entries in the cache whose expiration is older than `now`.
144 void Expire(std::chrono::steady_clock::time_point now);
145
146 // Return the global instance of the verbs cache.
147 static VerbsCache &Instance();
148
149private:
150 VerbsCache() = default;
151 VerbsCache(const VerbsCache &) = delete;
152 VerbsCache(VerbsCache &&) = delete;
153
154 // Background thread periodically invoking `Expire` on the cache.
155 static void ExpireThread();
156
157 // Invoked by the destructor of a static member. Triggered when the library
158 // is shutting down or is unloaded from the process.
159 static void Shutdown();
160
161 mutable std::atomic<uint64_t> m_cache_hit{0};
162 mutable std::atomic<uint64_t> m_cache_miss{0};
163
164 template<typename ... Bases>
165 struct overload : Bases ...
166 {
167 using is_transparent = void;
168 using Bases::operator() ... ;
169 };
170 using transparent_string_hash = overload<
171 std::hash<std::string>,
172 std::hash<std::string_view>
173 >;
174
175 struct VerbEntry {
176 std::chrono::steady_clock::time_point m_expiry;
177 HttpVerbs m_verbs;
178 };
179
180 mutable std::shared_mutex m_mutex;
181 mutable std::unordered_map<std::string, VerbEntry, VerbsCache::transparent_string_hash, std::equal_to<>> m_verbs_map;
182
183 static std::once_flag m_expiry_launch;
184 static VerbsCache g_cache;
185 static constexpr std::chrono::steady_clock::duration g_expiry_duration = std::chrono::hours(6);
186 static constexpr std::chrono::steady_clock::duration g_negative_expiry_duration = std::chrono::minutes(15);
187
188 // Mutex for managing the shutdown of the background thread
189 static std::mutex m_shutdown_lock;
190 // Condition variable managing the requested shutdown of the background thread.
191 static std::condition_variable m_shutdown_requested_cv;
192 // Flag indicating that a shutdown was requested.
193 static bool m_shutdown_requested;
194 // The cache expire thread
195 static std::thread m_expire_tid;
196 // shutdown trigger
197 static struct shutdown_s {
198 ~shutdown_s() { Shutdown(); }
199 } m_shutdowns;
200};
201
202 } // namespace XrdClHttp
203
204#endif // _XRDCLHTTP__OPTIONSCACHE_HH__
HttpVerbs & operator|=(HttpVerb verb)
void Expire(std::chrono::steady_clock::time_point now)
HttpVerbs Get(const std::string &url, const std::chrono::steady_clock::time_point &now=std::chrono::steady_clock::now()) const
static std::string_view GetUrlKey(const std::string &url, std::string &modified_url)
static const std::string GetVerbString(HttpVerb ctype)
void Put(const std::string &url, const HttpVerbs &verbs, const std::chrono::steady_clock::time_point &now=std::chrono::steady_clock::now()) const
static VerbsCache & Instance()