3 #include "crow/http_request.h"
4 #include "crow/http_response.h"
6 #include "crow/utility.h"
7 #include "crow/middlewares/cookie_parser.h"
9 #include <unordered_map>
10 #include <unordered_set>
22 #include <type_traits>
26 #ifdef CROW_CAN_USE_CPP17
34 using wrap_integral_t =
typename std::conditional<
35 std::is_integral<T>::value && !std::is_same<bool, T>::value
37 && !std::is_same<uint64_t, T>::value,
42 using wrap_char_t =
typename std::conditional<
43 std::is_same<typename std::decay<T>::type,
char*>::value,
44 std::string, T>::type;
48 using wrap_mv_t = wrap_char_t<wrap_integral_t<T>>;
56 #ifdef CROW_CAN_USE_CPP17
57 using multi_value_types = black_magic::S<bool, int64_t, double, std::string>;
62 json::wvalue json()
const
65 return std::visit([](
auto arg) {
66 return json::wvalue(arg);
71 static multi_value from_json(
const json::rvalue&);
73 std::string string()
const
76 return std::visit([](
auto arg) {
77 if constexpr (std::is_same_v<decltype(arg), std::string>)
80 return std::to_string(arg);
85 template<typename T, typename RT = wrap_mv_t<T>>
86 RT get(const T& fallback)
88 if (
const RT* val = std::get_if<RT>(&v_))
return *val;
92 template<
typename T,
typename RT = wrap_mv_t<T>>
95 v_ = RT(std::move(val));
98 typename multi_value_types::rebind<std::variant> v_;
101 inline multi_value multi_value::from_json(
const json::rvalue& rv)
103 using namespace json;
108 if (rv.nt() == num_type::Floating_point || rv.nt() == num_type::Double_precision_floating_point)
109 return multi_value{rv.d()};
110 else if (rv.nt() == num_type::Unsigned_integer)
111 return multi_value{int64_t(rv.u())};
113 return multi_value{rv.i()};
115 case type::False:
return multi_value{
false};
116 case type::True:
return multi_value{
true};
117 case type::String:
return multi_value{std::string(rv)};
118 default:
return multi_value{
false};
131 std::string string()
const {
return v_.dump(); }
133 template<
typename T,
typename RT = wrap_mv_t<T>>
134 RT get(
const T& fallback)
139 template<
typename T,
typename RT = wrap_mv_t<T>>
142 v_ = RT(std::move(val));
157 using DataPair = std::pair<uint64_t , std::string >;
161 void add(std::string key, uint64_t time)
163 auto it = times_.find(key);
164 if (it != times_.end()) remove(key);
166 queue_.insert({time, std::move(key)});
169 void remove(
const std::string& key)
171 auto it = times_.find(key);
172 if (it != times_.end())
174 queue_.erase({it->second, key});
182 if (queue_.empty())
return std::numeric_limits<uint64_t>::max();
183 return queue_.begin()->first;
186 std::string pop_first()
188 auto it = times_.find(queue_.begin()->second);
189 auto key = it->first;
191 queue_.erase(queue_.begin());
195 using iterator =
typename std::set<DataPair>::const_iterator;
197 iterator begin()
const {
return queue_.cbegin(); }
199 iterator end()
const {
return queue_.cend(); }
202 std::set<DataPair> queue_;
203 std::unordered_map<std::string, uint64_t> times_;
209 std::string session_id;
210 std::string requested_session_id;
212 std::unordered_map<std::string, multi_value> entries;
213 std::unordered_set<std::string> dirty;
216 bool requested_refresh;
221 std::recursive_mutex mutex;
226 template<
typename Store>
229 #ifdef CROW_CAN_USE_CPP17
230 using lock = std::scoped_lock<std::mutex>;
231 using rc_lock = std::scoped_lock<std::recursive_mutex>;
233 using lock = std::lock_guard<std::mutex>;
234 using rc_lock = std::lock_guard<std::recursive_mutex>;
240 std::recursive_mutex& mutex()
247 bool exists() {
return bool(node); }
251 auto get(
const std::string& key,
const F& fallback = F())
257 -> decltype(std::declval<session::multi_value>().get<F>(std::declval<F>()))
259 if (!node)
return fallback;
260 rc_lock l(node->mutex);
262 auto it = node->entries.find(key);
263 if (it != node->entries.end())
return it->second.get<F>(fallback);
269 void set(
const std::string& key, T value)
272 rc_lock l(node->mutex);
274 node->dirty.insert(key);
275 node->entries[key].set(std::move(value));
278 bool contains(
const std::string& key)
280 if (!node)
return false;
281 return node->entries.find(key) != node->entries.end();
285 template<
typename Func>
286 void apply(
const std::string& key,
const Func& f)
288 using traits = utility::function_traits<Func>;
289 using arg =
typename std::decay<typename traits::template arg<0>>::type;
290 using retv =
typename std::decay<typename traits::result_type>::type;
292 rc_lock l(node->mutex);
293 node->dirty.insert(key);
294 node->entries[key].set<retv>(f(node->entries[key].get(arg{})));
298 void remove(
const std::string& key)
301 rc_lock l(node->mutex);
302 node->dirty.insert(key);
303 node->entries.erase(key);
307 std::string string(
const std::string& key)
309 if (!node)
return "";
310 rc_lock l(node->mutex);
312 auto it = node->entries.find(key);
313 if (it != node->entries.end())
return it->second.string();
318 std::vector<std::string> keys()
320 if (!node)
return {};
321 rc_lock l(node->mutex);
323 std::vector<std::string> out;
324 for (
const auto& p : node->entries)
325 out.push_back(p.first);
331 void refresh_expiration()
334 node->requested_refresh =
true;
342 if (!node) node = std::make_shared<session::CachedSession>();
345 std::shared_ptr<session::CachedSession> node;
348 template<
typename... Ts>
353 id_length_(id_length),
355 store_(std::forward<Ts>(ts)...), mutex_(new std::mutex{})
358 template<
typename... Ts>
361 CookieParser::Cookie(
"session").path(
"/").max_age( 30 * 24 * 60 * 60),
363 std::forward<Ts>(ts)...)
366 template<
typename AllContext>
367 void before_handle(request& , response& , context& ctx, AllContext& all_ctx)
371 auto& cookies = all_ctx.template get<CookieParser>();
372 auto session_id = load_id(cookies);
373 if (session_id ==
"")
return;
376 auto it = cache_.find(session_id);
377 if (it != cache_.end())
379 it->second->referrers++;
380 ctx.node = it->second;
385 if (!store_.contains(session_id))
return;
387 auto node = std::make_shared<session::CachedSession>();
388 node->session_id = session_id;
397 CROW_LOG_ERROR <<
"Exception occurred during session load";
402 cache_[session_id] = node;
405 template<
typename AllContext>
406 void after_handle(request& , response& , context& ctx, AllContext& all_ctx)
409 if (!ctx.node || --ctx.node->referrers > 0)
return;
410 ctx.node->requested_refresh |= ctx.node->session_id ==
"";
413 if (ctx.node->session_id ==
"")
416 ctx.node->session_id = std::move(ctx.node->requested_session_id);
417 if (ctx.node->session_id ==
"")
419 ctx.node->session_id = utility::random_alphanum(id_length_);
424 cache_.erase(ctx.node->session_id);
427 if (ctx.node->requested_refresh)
429 auto& cookies = all_ctx.template get<CookieParser>();
430 store_id(cookies, ctx.node->session_id);
435 store_.save(*ctx.node);
439 CROW_LOG_ERROR <<
"Exception occurred during session save";
445 std::string next_id()
450 id = utility::random_alphanum(id_length_);
451 }
while (store_.contains(
id));
455 std::string load_id(
const CookieParser::context& cookies)
457 return cookies.get_cookie(cookie_.name());
460 void store_id(CookieParser::context& cookies,
const std::string& session_id)
462 cookie_.value(session_id);
463 cookies.set_cookie(cookie_);
470 CookieParser::Cookie cookie_;
475 std::unique_ptr<std::mutex> mutex_;
476 std::unordered_map<std::string, std::shared_ptr<session::CachedSession>> cache_;
487 cn.entries = std::move(entries[cn.session_id]);
493 entries[cn.session_id] = std::move(cn.entries);
497 bool contains(
const std::string& key)
499 return entries.count(key) > 0;
502 std::unordered_map<std::string, std::unordered_map<std::string, session::multi_value>> entries;
509 FileStore(
const std::string& folder, uint64_t expiration_seconds = 30 * 24 * 60 * 60):
510 path_(folder), expiration_seconds_(expiration_seconds)
512 std::ifstream ifs(get_filename(
".expirations",
false));
514 auto current_ts = chrono_time();
517 while (ifs >> key >> time)
519 if (current_ts > time)
523 else if (contains(key))
525 expirations_.add(key, time);
532 std::ofstream ofs(get_filename(
".expirations",
false), std::ios::trunc);
533 for (
const auto& p : expirations_)
534 ofs << p.second <<
" " << p.first <<
"\n";
539 void handle_expired()
542 auto current_ts = chrono_time();
543 while (current_ts > expirations_.peek_first() && deleted < 3)
545 evict(expirations_.pop_first());
554 std::ifstream file(get_filename(cn.session_id));
556 std::stringstream buffer;
557 buffer << file.rdbuf() << std::endl;
559 for (
const auto& p :
json::load(buffer.str()))
560 cn.entries[p.key()] = session::multi_value::from_json(p);
565 if (cn.requested_refresh)
566 expirations_.add(cn.session_id, chrono_time() + expiration_seconds_);
567 if (cn.dirty.empty())
return;
569 std::ofstream file(get_filename(cn.session_id));
571 for (
const auto& p : cn.entries)
572 jw[p.first] = p.second.json();
573 file << jw.dump() << std::flush;
576 std::string get_filename(
const std::string& key,
bool suffix =
true)
578 return utility::join_path(path_, key + (suffix ?
".json" :
""));
581 bool contains(
const std::string& key)
583 std::ifstream file(get_filename(key));
587 void evict(
const std::string& key)
589 std::remove(get_filename(key).c_str());
592 uint64_t chrono_time()
const
594 return std::chrono::duration_cast<std::chrono::seconds>(
595 std::chrono::system_clock::now().time_since_epoch())
600 uint64_t expiration_seconds_;
JSON read value.
Definition: json.h:278
JSON write value.
Definition: json.h:1291
template_t load(const std::string &filename)
Open, read and renders a file using a mustache compiler. It also sanitize the input before compilatio...
Definition: mustache.h:812
The main namespace of the library. In this namespace is defined the most important classes and functi...
Definition: cookie_parser.h:37
Definition: cookie_parser.h:34
Definition: session.h:508
InMemoryStore stores all entries in memory.
Definition: session.h:481
Definition: session.h:238
Definition: session.h:228
CachedSessions are shared across requests.
Definition: session.h:208
Expiration tracker keeps track of soonest-to-expire keys.
Definition: session.h:156
uint64_t peek_first() const
Get expiration time of soonest-to-expire entry.
Definition: session.h:180
void add(std::string key, uint64_t time)
Definition: session.h:161
Definition: session.h:126