54 using multi_value_types = black_magic::S<bool, int64_t, double, std::string>;
122 using DataPair = std::pair<uint64_t , std::string >;
126 void add(std::string key, uint64_t time)
128 auto it = times_.find(key);
129 if (it != times_.end()) remove(key);
131 queue_.insert({time, std::move(key)});
134 void remove(
const std::string& key)
136 auto it = times_.find(key);
137 if (it != times_.end())
139 queue_.erase({it->second, key});
147 if (queue_.empty())
return std::numeric_limits<uint64_t>::max();
148 return queue_.begin()->first;
152 uint64_t
get(
const std::string& key)
const
154 auto it = times_.find(key);
155 if (it == times_.end())
return std::numeric_limits<uint64_t>::max();
159 std::string pop_first()
161 auto it = times_.find(queue_.begin()->second);
162 auto key = it->first;
164 queue_.erase(queue_.begin());
168 using iterator =
typename std::set<DataPair>::const_iterator;
170 iterator begin()
const {
return queue_.cbegin(); }
172 iterator end()
const {
return queue_.cend(); }
175 std::set<DataPair> queue_;
176 std::unordered_map<std::string, uint64_t> times_;
202 using lock = std::scoped_lock<std::mutex>;
203 using rc_lock = std::scoped_lock<std::recursive_mutex>;
208 std::recursive_mutex& mutex()
215 bool exists() {
return bool(node); }
219 auto get(
const std::string& key,
const F& fallback = F())
225 ->
decltype(std::declval<session::multi_value>().get<F>(std::declval<F>()))
227 if (!node)
return fallback;
228 rc_lock l(node->mutex);
230 auto it = node->entries.find(key);
231 if (it != node->entries.end())
return it->second.get<F>(fallback);
237 void set(
const std::string& key, T value)
240 rc_lock l(node->mutex);
242 node->dirty.insert(key);
243 node->entries[key].set(std::move(value));
246 bool contains(
const std::string& key)
248 if (!node)
return false;
249 return node->entries.find(key) != node->entries.end();
253 template<
typename Func>
254 void apply(
const std::string& key,
const Func& f)
256 using traits = utility::function_traits<Func>;
257 using arg =
typename std::decay<typename traits::template arg<0>>::type;
258 using retv =
typename std::decay<typename traits::result_type>::type;
260 rc_lock l(node->mutex);
261 node->dirty.insert(key);
262 node->entries[key].set<retv>(f(node->entries[key].get(arg{})));
266 void remove(
const std::string& key)
269 rc_lock l(node->mutex);
270 node->dirty.insert(key);
271 node->entries.erase(key);
275 std::string string(
const std::string& key)
277 if (!node)
return "";
278 rc_lock l(node->mutex);
280 auto it = node->entries.find(key);
281 if (it != node->entries.end())
return it->second.string();
286 std::vector<std::string> keys()
288 if (!node)
return {};
289 rc_lock l(node->mutex);
291 std::vector<std::string> out;
292 for (
const auto& p : node->entries)
293 out.push_back(p.first);
299 void refresh_expiration()
302 node->requested_refresh =
true;
310 if (!node) node = std::make_shared<session::CachedSession>();
313 std::shared_ptr<session::CachedSession> node;
316 template<
typename... Ts>
321 id_length_(id_length),
323 store_(std::forward<Ts>(ts)...), mutex_(new std::mutex{})
326 template<
typename... Ts>
331 .max_age( 30 * 24 * 60 * 60)
335 std::forward<Ts>(ts)...)
338 template<
typename AllContext>
339 void before_handle(request& , response& , context& ctx, AllContext& all_ctx)
343 auto& cookies = all_ctx.template get<CookieParser>();
344 auto session_id = load_id(cookies);
345 if (session_id ==
"")
return;
348 auto it = cache_.find(session_id);
349 if (it != cache_.end())
351 it->second->referrers++;
352 ctx.node = it->second;
357 if (!store_.contains(session_id))
return;
359 auto node = std::make_shared<session::CachedSession>();
360 node->session_id = session_id;
369 CROW_LOG_ERROR <<
"Exception occurred during session load";
374 cache_[session_id] = node;
377 template<
typename AllContext>
378 void after_handle(request& , response& , context& ctx, AllContext& all_ctx)
381 if (!ctx.node || --ctx.node->referrers > 0)
return;
382 ctx.node->requested_refresh |= ctx.node->session_id ==
"";
385 if (ctx.node->session_id ==
"")
388 ctx.node->session_id = std::move(ctx.node->requested_session_id);
389 if (ctx.node->session_id ==
"")
391 ctx.node->session_id = utility::random_alphanum(id_length_);
396 cache_.erase(ctx.node->session_id);
399 if (ctx.node->requested_refresh)
401 auto& cookies = all_ctx.template get<CookieParser>();
402 store_id(cookies, ctx.node->session_id);
407 store_.save(*ctx.node);
411 CROW_LOG_ERROR <<
"Exception occurred during session save";
417 std::string next_id()
422 id = utility::random_alphanum(id_length_);
423 }
while (store_.contains(
id));
427 std::string load_id(
const CookieParser::context& cookies)
429 return cookies.get_cookie(cookie_.name());
432 void store_id(CookieParser::context& cookies,
const std::string& session_id)
434 cookie_.value(session_id);
435 cookies.set_cookie(cookie_);
442 CookieParser::Cookie cookie_;
447 std::unique_ptr<std::mutex> mutex_;
448 std::unordered_map<std::string, std::shared_ptr<session::CachedSession>> cache_;
481 FileStore(
const std::string& folder, uint64_t expiration_seconds = 30 * 24 * 60 * 60):
482 path_(folder), expiration_seconds_(expiration_seconds)
484 std::ifstream ifs(get_filename(
".expirations",
false));
486 auto current_ts = chrono_time();
489 while (ifs >> key >> time)
491 if (current_ts > time)
495 else if (contains(key))
497 expirations_.
add(key, time);
504 std::ofstream ofs(get_filename(
".expirations",
false), std::ios::trunc);
505 for (
const auto& p : expirations_)
506 ofs << p.second <<
" " << p.first <<
"\n";
511 void handle_expired()
514 auto current_ts = chrono_time();
515 while (current_ts > expirations_.
peek_first() && deleted < 3)
517 evict(expirations_.pop_first());
526 std::ifstream file(get_filename(cn.session_id));
528 std::stringstream buffer;
529 buffer << file.rdbuf() << std::endl;
531 for (
const auto& p : json::load(buffer.str()))
532 cn.entries[p.key()] = session::multi_value::from_json(p);
537 if (cn.requested_refresh)
538 expirations_.
add(cn.session_id, chrono_time() + expiration_seconds_);
539 if (cn.dirty.empty())
return;
541 std::ofstream file(get_filename(cn.session_id));
543 for (
const auto& p : cn.entries)
544 jw[p.first] = p.second.json();
545 file << jw.dump() << std::flush;
548 std::string get_filename(
const std::string& key,
bool suffix =
true)
550 std::string filename{key};
552 utility::sanitize_filename(filename);
555 filename.append(
".json");
557 return (path_/filename).string();
560 bool contains(
const std::string& key)
563 if (chrono_time() > expirations_.
get(key))
565 expirations_.remove(key);
570 std::ifstream file(get_filename(key));
574 void evict(
const std::string& key)
576 std::remove(get_filename(key).c_str());
580 uint64_t chrono_time()
const
582 return std::chrono::duration_cast<std::chrono::seconds>(
583 std::chrono::system_clock::now().time_since_epoch())
587 std::filesystem::path path_;
588 uint64_t expiration_seconds_;