added crow

This commit is contained in:
2025-12-14 21:34:10 +01:00
parent 8df3c5479b
commit 6f1d51cabc
39 changed files with 15400 additions and 0 deletions

View File

@@ -0,0 +1,321 @@
#pragma once
#include <iomanip>
#include <memory>
#include "crow/utility.h"
#include "crow/http_request.h"
#include "crow/http_response.h"
namespace crow
{
// Any middleware requires following 3 members:
// struct context;
// storing data for the middleware; can be read from another middleware or handlers
// before_handle
// called before handling the request.
// if res.end() is called, the operation is halted.
// (still call after_handle of this middleware)
// 2 signatures:
// void before_handle(request& req, response& res, context& ctx)
// if you only need to access this middlewares context.
// template <typename AllContext>
// void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
// you can access another middlewares' context by calling `all_ctx.template get<MW>()'
// ctx == all_ctx.template get<CurrentMiddleware>()
// after_handle
// called after handling the request.
// void after_handle(request& req, response& res, context& ctx)
// template <typename AllContext>
// void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
struct CookieParser
{
// Cookie stores key, value and attributes
struct Cookie
{
enum class SameSitePolicy
{
Strict,
Lax,
None
};
template<typename U>
Cookie(const std::string& key, U&& value):
Cookie()
{
key_ = key;
value_ = std::forward<U>(value);
}
Cookie(const std::string& key):
Cookie(key, "") {}
// format cookie to HTTP header format
std::string dump() const
{
const static char* HTTP_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S GMT";
std::stringstream ss;
ss << key_ << '=';
ss << (value_.empty() ? "\"\"" : value_);
dumpString(ss, !domain_.empty(), "Domain=", domain_);
dumpString(ss, !path_.empty(), "Path=", path_);
dumpString(ss, secure_, "Secure");
dumpString(ss, httponly_, "HttpOnly");
if (expires_at_)
{
ss << DIVIDER << "Expires="
<< std::put_time(expires_at_.get(), HTTP_DATE_FORMAT);
}
if (max_age_)
{
ss << DIVIDER << "Max-Age=" << *max_age_;
}
if (same_site_)
{
ss << DIVIDER << "SameSite=";
switch (*same_site_)
{
case SameSitePolicy::Strict:
ss << "Strict";
break;
case SameSitePolicy::Lax:
ss << "Lax";
break;
case SameSitePolicy::None:
ss << "None";
break;
}
}
return ss.str();
}
const std::string& name()
{
return key_;
}
template<typename U>
Cookie& value(U&& value)
{
value_ = std::forward<U>(value);
return *this;
}
// Expires attribute
Cookie& expires(const std::tm& time)
{
expires_at_ = std::unique_ptr<std::tm>(new std::tm(time));
return *this;
}
// Max-Age attribute
Cookie& max_age(long long seconds)
{
max_age_ = std::unique_ptr<long long>(new long long(seconds));
return *this;
}
// Domain attribute
Cookie& domain(const std::string& name)
{
domain_ = name;
return *this;
}
// Path attribute
Cookie& path(const std::string& path)
{
path_ = path;
return *this;
}
// Secured attribute
Cookie& secure()
{
secure_ = true;
return *this;
}
// HttpOnly attribute
Cookie& httponly()
{
httponly_ = true;
return *this;
}
// SameSite attribute
Cookie& same_site(SameSitePolicy ssp)
{
same_site_ = std::unique_ptr<SameSitePolicy>(new SameSitePolicy(ssp));
return *this;
}
Cookie(const Cookie& c):
key_(c.key_),
value_(c.value_),
domain_(c.domain_),
path_(c.path_),
secure_(c.secure_),
httponly_(c.httponly_)
{
if (c.max_age_)
max_age_ = std::unique_ptr<long long>(new long long(*c.max_age_));
if (c.expires_at_)
expires_at_ = std::unique_ptr<std::tm>(new std::tm(*c.expires_at_));
if (c.same_site_)
same_site_ = std::unique_ptr<SameSitePolicy>(new SameSitePolicy(*c.same_site_));
}
private:
Cookie() = default;
static void dumpString(std::stringstream& ss, bool cond, const char* prefix,
const std::string& value = "")
{
if (cond)
{
ss << DIVIDER << prefix << value;
}
}
private:
std::string key_;
std::string value_;
std::unique_ptr<long long> max_age_{};
std::string domain_ = "";
std::string path_ = "";
bool secure_ = false;
bool httponly_ = false;
std::unique_ptr<std::tm> expires_at_{};
std::unique_ptr<SameSitePolicy> same_site_{};
static constexpr const char* DIVIDER = "; ";
};
struct context
{
std::unordered_map<std::string, std::string> jar;
std::string get_cookie(const std::string& key) const
{
auto cookie = jar.find(key);
if (cookie != jar.end())
return cookie->second;
return {};
}
template<typename U>
Cookie& set_cookie(const std::string& key, U&& value)
{
cookies_to_add.emplace_back(key, std::forward<U>(value));
return cookies_to_add.back();
}
Cookie& set_cookie(Cookie cookie)
{
cookies_to_add.push_back(std::move(cookie));
return cookies_to_add.back();
}
private:
friend struct CookieParser;
std::vector<Cookie> cookies_to_add;
};
void before_handle(request& req, response& res, context& ctx)
{
const int count = req.headers.count("Cookie");
if (!count)
return;
if (count > 1)
{
res.code = 400;
res.end();
return;
}
const std::string_view cookies_sv = req.get_header_value("Cookie");
size_t pos = 0;
while (pos < cookies_sv.size())
{
const size_t pos_equal = cookies_sv.find('=', pos);
if (pos_equal == std::string_view::npos) {
break;
}
std::string_view name_sv = cookies_sv.substr(pos, pos_equal - pos);
name_sv = utility::trim(name_sv);
pos = pos_equal + 1;
if (pos == cookies_sv.size()) {
break;
}
const size_t pos_semicolon = cookies_sv.find(';', pos);
std::string_view value_sv;
if (pos_semicolon == std::string_view::npos) {
value_sv = cookies_sv.substr(pos);
pos = cookies_sv.size();
} else {
value_sv = cookies_sv.substr(pos, pos_semicolon - pos);
pos = pos_semicolon + 1;
}
value_sv = utility::trim(value_sv);
if (!value_sv.empty() && value_sv.front() == '"' && value_sv.back() == '"')
{
if (value_sv.size() >= 2) {
value_sv.remove_prefix(1);
value_sv.remove_suffix(1);
} else {
value_sv = value_sv.substr(0,0);
}
}
ctx.jar.emplace(std::string(name_sv), std::string(value_sv));
}
}
void after_handle(request& /*req*/, response& res, context& ctx)
{
for (const auto& cookie : ctx.cookies_to_add)
{
res.add_header("Set-Cookie", cookie.dump());
}
}
};
/*
App<CookieParser, AnotherJarMW> app;
A B C
A::context
int aa;
ctx1 : public A::context
ctx2 : public ctx1, public B::context
ctx3 : public ctx2, public C::context
C depends on A
C::handle
context.aaa
App::context : private CookieParser::context, ...
{
jar
}
SimpleApp
*/
} // namespace crow

View File

@@ -0,0 +1,237 @@
#pragma once
#include "crow/common.h"
#include "crow/http_request.h"
#include "crow/http_response.h"
#include "crow/routing.h"
namespace crow
{
struct CORSHandler;
/// Used for tuning CORS policies
struct CORSRules
{
friend struct crow::CORSHandler;
/// Set Access-Control-Allow-Origin. Default is "*"
CORSRules& origin(const std::string& origin)
{
origin_ = origin;
return *this;
}
/// Set Access-Control-Allow-Methods. Default is "*"
CORSRules& methods(crow::HTTPMethod method)
{
add_list_item(methods_, crow::method_name(method));
return *this;
}
/// Set Access-Control-Allow-Methods. Default is "*"
template<typename... Methods>
CORSRules& methods(crow::HTTPMethod method, Methods... method_list)
{
add_list_item(methods_, crow::method_name(method));
methods(method_list...);
return *this;
}
/// Set Access-Control-Allow-Headers. Default is "*"
CORSRules& headers(const std::string& header)
{
add_list_item(headers_, header);
return *this;
}
/// Set Access-Control-Allow-Headers. Default is "*"
template<typename... Headers>
CORSRules& headers(const std::string& header, Headers... header_list)
{
add_list_item(headers_, header);
headers(header_list...);
return *this;
}
/// Set Access-Control-Expose-Headers. Default is none
CORSRules& expose(const std::string& header)
{
add_list_item(exposed_headers_, header);
return *this;
}
/// Set Access-Control-Expose-Headers. Default is none
template<typename... Headers>
CORSRules& expose(const std::string& header, Headers... header_list)
{
add_list_item(exposed_headers_, header);
expose(header_list...);
return *this;
}
/// Set Access-Control-Max-Age. Default is none
CORSRules& max_age(int max_age)
{
max_age_ = std::to_string(max_age);
return *this;
}
/// Enable Access-Control-Allow-Credentials
CORSRules& allow_credentials()
{
allow_credentials_ = true;
return *this;
}
/// Ignore CORS and don't send any headers
void ignore()
{
ignore_ = true;
}
/// Handle CORS on specific prefix path
CORSRules& prefix(const std::string& prefix);
/// Handle CORS for specific blueprint
CORSRules& blueprint(const Blueprint& bp);
/// Global CORS policy
CORSRules& global();
private:
CORSRules() = delete;
CORSRules(CORSHandler* handler):
handler_(handler) {}
/// build comma separated list
void add_list_item(std::string& list, const std::string& val)
{
if (list == "*") list = "";
if (list.size() > 0) list += ", ";
list += val;
}
/// Set header `key` to `value` if it is not set
void set_header_no_override(const std::string& key, const std::string& value, crow::response& res)
{
if (value.size() == 0) return;
if (!get_header_value(res.headers, key).empty()) return;
res.add_header(key, value);
}
/// Set response headers
void apply(const request& req, response& res)
{
if (ignore_) return;
set_header_no_override("Access-Control-Allow-Methods", methods_, res);
set_header_no_override("Access-Control-Allow-Headers", headers_, res);
set_header_no_override("Access-Control-Expose-Headers", exposed_headers_, res);
set_header_no_override("Access-Control-Max-Age", max_age_, res);
bool origin_set = false;
if (req.method != HTTPMethod::Options)
{
if (allow_credentials_)
{
set_header_no_override("Access-Control-Allow-Credentials", "true", res);
if (origin_ == "*")
{
set_header_no_override("Access-Control-Allow-Origin", req.get_header_value("Origin"), res);
origin_set = true;
}
}
}
if( !origin_set){
set_header_no_override("Access-Control-Allow-Origin", origin_, res);
}
}
bool ignore_ = false;
// TODO: support multiple origins that are dynamically selected
std::string origin_ = "*";
std::string methods_ = "*";
std::string headers_ = "*";
std::string exposed_headers_;
std::string max_age_;
bool allow_credentials_ = false;
CORSHandler* handler_;
};
/// CORSHandler is a global middleware for setting CORS headers.
///
/// By default, it sets Access-Control-Allow-Origin/Methods/Headers to "*".
/// The default behaviour can be changed with the `global()` cors rule.
/// Additional rules for prexies can be added with `prefix()`.
struct CORSHandler
{
struct context
{};
void before_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/)
{}
void after_handle(crow::request& req, crow::response& res, context& /*ctx*/)
{
auto& rule = find_rule(req.url);
rule.apply(req, res);
}
/// Handle CORS on a specific prefix path
CORSRules& prefix(const std::string& prefix)
{
rules.emplace_back(prefix, CORSRules(this));
return rules.back().second;
}
/// Handle CORS for a specific blueprint
CORSRules& blueprint(const Blueprint& bp)
{
rules.emplace_back(bp.prefix(), CORSRules(this));
return rules.back().second;
}
/// Get the global CORS policy
CORSRules& global()
{
return default_;
}
private:
CORSRules& find_rule(const std::string& path)
{
// TODO: use a trie in case of many rules
for (auto& rule : rules)
{
// Check if path starts with a rules prefix
if (path.rfind(rule.first, 0) == 0)
{
return rule.second;
}
}
return default_;
}
std::vector<std::pair<std::string, CORSRules>> rules;
CORSRules default_ = CORSRules(this);
};
inline CORSRules& CORSRules::prefix(const std::string& prefix)
{
return handler_->prefix(prefix);
}
inline CORSRules& CORSRules::blueprint(const Blueprint& bp)
{
return handler_->blueprint(bp);
}
inline CORSRules& CORSRules::global()
{
return handler_->global();
}
} // namespace crow

View File

@@ -0,0 +1,564 @@
#pragma once
#include "crow/http_request.h"
#include "crow/http_response.h"
#include "crow/json.h"
#include "crow/utility.h"
#include "crow/middlewares/cookie_parser.h"
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <queue>
#include <memory>
#include <string>
#include <cstdio>
#include <mutex>
#include <fstream>
#include <sstream>
#include <type_traits>
#include <functional>
#include <chrono>
#include <variant>
namespace
{
// convert all integer values to int64_t
template<typename T>
using wrap_integral_t = typename std::conditional<
std::is_integral<T>::value && !std::is_same<bool, T>::value
// except for uint64_t because that could lead to overflow on conversion
&& !std::is_same<uint64_t, T>::value,
int64_t, T>::type;
// convert char[]/char* to std::string
template<typename T>
using wrap_char_t = typename std::conditional<
std::is_same<typename std::decay<T>::type, char*>::value,
std::string, T>::type;
// Upgrade to correct type for multi_variant use
template<typename T>
using wrap_mv_t = wrap_char_t<wrap_integral_t<T>>;
} // namespace
namespace crow
{
namespace session
{
using multi_value_types = black_magic::S<bool, int64_t, double, std::string>;
/// A multi_value is a safe variant wrapper with json conversion support
struct multi_value
{
json::wvalue json() const
{
// clang-format off
return std::visit([](auto arg) {
return json::wvalue(arg);
}, v_);
// clang-format on
}
static multi_value from_json(const json::rvalue&);
std::string string() const
{
// clang-format off
return std::visit([](auto arg) {
if constexpr (std::is_same_v<decltype(arg), std::string>)
return arg;
else
return std::to_string(arg);
}, v_);
// clang-format on
}
template<typename T, typename RT = wrap_mv_t<T>>
RT get(const T& fallback)
{
if (const RT* val = std::get_if<RT>(&v_)) return *val;
return fallback;
}
template<typename T, typename RT = wrap_mv_t<T>>
void set(T val)
{
v_ = RT(std::move(val));
}
typename multi_value_types::rebind<std::variant> v_;
};
inline multi_value multi_value::from_json(const json::rvalue& rv)
{
using namespace json;
switch (rv.t())
{
case type::Number:
{
if (rv.nt() == num_type::Floating_point || rv.nt() == num_type::Double_precision_floating_point)
return multi_value{rv.d()};
else if (rv.nt() == num_type::Unsigned_integer)
return multi_value{int64_t(rv.u())};
else
return multi_value{rv.i()};
}
case type::False: return multi_value{false};
case type::True: return multi_value{true};
case type::String: return multi_value{std::string(rv)};
default: return multi_value{false};
}
}
/// Expiration tracker keeps track of soonest-to-expire keys
struct ExpirationTracker
{
using DataPair = std::pair<uint64_t /*time*/, std::string /*key*/>;
/// Add key with time to tracker.
/// If the key is already present, it will be updated
void add(std::string key, uint64_t time)
{
auto it = times_.find(key);
if (it != times_.end()) remove(key);
times_[key] = time;
queue_.insert({time, std::move(key)});
}
void remove(const std::string& key)
{
auto it = times_.find(key);
if (it != times_.end())
{
queue_.erase({it->second, key});
times_.erase(it);
}
}
/// Get expiration time of soonest-to-expire entry
uint64_t peek_first() const
{
if (queue_.empty()) return std::numeric_limits<uint64_t>::max();
return queue_.begin()->first;
}
std::string pop_first()
{
auto it = times_.find(queue_.begin()->second);
auto key = it->first;
times_.erase(it);
queue_.erase(queue_.begin());
return key;
}
using iterator = typename std::set<DataPair>::const_iterator;
iterator begin() const { return queue_.cbegin(); }
iterator end() const { return queue_.cend(); }
private:
std::set<DataPair> queue_;
std::unordered_map<std::string, uint64_t> times_;
};
/// CachedSessions are shared across requests
struct CachedSession
{
std::string session_id;
std::string requested_session_id; // session hasn't been created yet, but a key was requested
std::unordered_map<std::string, multi_value> entries;
std::unordered_set<std::string> dirty; // values that were changed after last load
void* store_data;
bool requested_refresh;
// number of references held - used for correctly destroying the cache.
// No need to be atomic, all SessionMiddleware accesses are synchronized
int referrers;
std::recursive_mutex mutex;
};
} // namespace session
// SessionMiddleware allows storing securely and easily small snippets of user information
template<typename Store>
struct SessionMiddleware
{
using lock = std::scoped_lock<std::mutex>;
using rc_lock = std::scoped_lock<std::recursive_mutex>;
struct context
{
// Get a mutex for locking this session
std::recursive_mutex& mutex()
{
check_node();
return node->mutex;
}
// Check whether this session is already present
bool exists() { return bool(node); }
// Get a value by key or fallback if it doesn't exist or is of another type
template<typename F>
auto get(const std::string& key, const F& fallback = F())
// This trick lets the multi_value deduce the return type from the fallback
// which allows both:
// context.get<std::string>("key")
// context.get("key", "") -> char[] is transformed into string by multivalue
// to return a string
-> decltype(std::declval<session::multi_value>().get<F>(std::declval<F>()))
{
if (!node) return fallback;
rc_lock l(node->mutex);
auto it = node->entries.find(key);
if (it != node->entries.end()) return it->second.get<F>(fallback);
return fallback;
}
// Set a value by key
template<typename T>
void set(const std::string& key, T value)
{
check_node();
rc_lock l(node->mutex);
node->dirty.insert(key);
node->entries[key].set(std::move(value));
}
bool contains(const std::string& key)
{
if (!node) return false;
return node->entries.find(key) != node->entries.end();
}
// Atomically mutate a value with a function
template<typename Func>
void apply(const std::string& key, const Func& f)
{
using traits = utility::function_traits<Func>;
using arg = typename std::decay<typename traits::template arg<0>>::type;
using retv = typename std::decay<typename traits::result_type>::type;
check_node();
rc_lock l(node->mutex);
node->dirty.insert(key);
node->entries[key].set<retv>(f(node->entries[key].get(arg{})));
}
// Remove a value from the session
void remove(const std::string& key)
{
if (!node) return;
rc_lock l(node->mutex);
node->dirty.insert(key);
node->entries.erase(key);
}
// Format value by key as a string
std::string string(const std::string& key)
{
if (!node) return "";
rc_lock l(node->mutex);
auto it = node->entries.find(key);
if (it != node->entries.end()) return it->second.string();
return "";
}
// Get a list of keys present in session
std::vector<std::string> keys()
{
if (!node) return {};
rc_lock l(node->mutex);
std::vector<std::string> out;
for (const auto& p : node->entries)
out.push_back(p.first);
return out;
}
// Delay expiration by issuing another cookie with an updated expiration time
// and notifying the store
void refresh_expiration()
{
if (!node) return;
node->requested_refresh = true;
}
private:
friend struct SessionMiddleware;
void check_node()
{
if (!node) node = std::make_shared<session::CachedSession>();
}
std::shared_ptr<session::CachedSession> node;
};
template<typename... Ts>
SessionMiddleware(
CookieParser::Cookie cookie,
int id_length,
Ts... ts):
id_length_(id_length),
cookie_(cookie),
store_(std::forward<Ts>(ts)...), mutex_(new std::mutex{})
{}
template<typename... Ts>
SessionMiddleware(Ts... ts):
SessionMiddleware(
CookieParser::Cookie("session").path("/").max_age(/*month*/ 30 * 24 * 60 * 60),
/*id_length */ 20, // around 10^34 possible combinations, but small enough to fit into SSO
std::forward<Ts>(ts)...)
{}
template<typename AllContext>
void before_handle(request& /*req*/, response& /*res*/, context& ctx, AllContext& all_ctx)
{
lock l(*mutex_);
auto& cookies = all_ctx.template get<CookieParser>();
auto session_id = load_id(cookies);
if (session_id == "") return;
// search entry in cache
auto it = cache_.find(session_id);
if (it != cache_.end())
{
it->second->referrers++;
ctx.node = it->second;
return;
}
// check this is a valid entry before loading
if (!store_.contains(session_id)) return;
auto node = std::make_shared<session::CachedSession>();
node->session_id = session_id;
node->referrers = 1;
try
{
store_.load(*node);
}
catch (...)
{
CROW_LOG_ERROR << "Exception occurred during session load";
return;
}
ctx.node = node;
cache_[session_id] = node;
}
template<typename AllContext>
void after_handle(request& /*req*/, response& /*res*/, context& ctx, AllContext& all_ctx)
{
lock l(*mutex_);
if (!ctx.node || --ctx.node->referrers > 0) return;
ctx.node->requested_refresh |= ctx.node->session_id == "";
// generate new id
if (ctx.node->session_id == "")
{
// check for requested id
ctx.node->session_id = std::move(ctx.node->requested_session_id);
if (ctx.node->session_id == "")
{
ctx.node->session_id = utility::random_alphanum(id_length_);
}
}
else
{
cache_.erase(ctx.node->session_id);
}
if (ctx.node->requested_refresh)
{
auto& cookies = all_ctx.template get<CookieParser>();
store_id(cookies, ctx.node->session_id);
}
try
{
store_.save(*ctx.node);
}
catch (...)
{
CROW_LOG_ERROR << "Exception occurred during session save";
return;
}
}
private:
std::string next_id()
{
std::string id;
do
{
id = utility::random_alphanum(id_length_);
} while (store_.contains(id));
return id;
}
std::string load_id(const CookieParser::context& cookies)
{
return cookies.get_cookie(cookie_.name());
}
void store_id(CookieParser::context& cookies, const std::string& session_id)
{
cookie_.value(session_id);
cookies.set_cookie(cookie_);
}
private:
int id_length_;
// prototype for cookie
CookieParser::Cookie cookie_;
Store store_;
// mutexes are immovable
std::unique_ptr<std::mutex> mutex_;
std::unordered_map<std::string, std::shared_ptr<session::CachedSession>> cache_;
};
/// InMemoryStore stores all entries in memory
struct InMemoryStore
{
// Load a value into the session cache.
// A load is always followed by a save, no loads happen consecutively
void load(session::CachedSession& cn)
{
// load & stores happen sequentially, so moving is safe
cn.entries = std::move(entries[cn.session_id]);
}
// Persist session data
void save(session::CachedSession& cn)
{
entries[cn.session_id] = std::move(cn.entries);
// cn.dirty is a list of changed keys since the last load
}
bool contains(const std::string& key)
{
return entries.count(key) > 0;
}
std::unordered_map<std::string, std::unordered_map<std::string, session::multi_value>> entries;
};
// FileStore stores all data as json files in a folder.
// Files are deleted after expiration. Expiration refreshes are automatically picked up.
struct FileStore
{
FileStore(const std::string& folder, uint64_t expiration_seconds = /*month*/ 30 * 24 * 60 * 60):
path_(folder), expiration_seconds_(expiration_seconds)
{
std::ifstream ifs(get_filename(".expirations", false));
auto current_ts = chrono_time();
std::string key;
uint64_t time;
while (ifs >> key >> time)
{
if (current_ts > time)
{
evict(key);
}
else if (contains(key))
{
expirations_.add(key, time);
}
}
}
~FileStore()
{
std::ofstream ofs(get_filename(".expirations", false), std::ios::trunc);
for (const auto& p : expirations_)
ofs << p.second << " " << p.first << "\n";
}
// Delete expired entries
// At most 3 to prevent freezes
void handle_expired()
{
int deleted = 0;
auto current_ts = chrono_time();
while (current_ts > expirations_.peek_first() && deleted < 3)
{
evict(expirations_.pop_first());
deleted++;
}
}
void load(session::CachedSession& cn)
{
handle_expired();
std::ifstream file(get_filename(cn.session_id));
std::stringstream buffer;
buffer << file.rdbuf() << std::endl;
for (const auto& p : json::load(buffer.str()))
cn.entries[p.key()] = session::multi_value::from_json(p);
}
void save(session::CachedSession& cn)
{
if (cn.requested_refresh)
expirations_.add(cn.session_id, chrono_time() + expiration_seconds_);
if (cn.dirty.empty()) return;
std::ofstream file(get_filename(cn.session_id));
json::wvalue jw;
for (const auto& p : cn.entries)
jw[p.first] = p.second.json();
file << jw.dump() << std::flush;
}
std::string get_filename(const std::string& key, bool suffix = true)
{
return utility::join_path(path_, key + (suffix ? ".json" : ""));
}
bool contains(const std::string& key)
{
std::ifstream file(get_filename(key));
return file.good();
}
void evict(const std::string& key)
{
std::remove(get_filename(key).c_str());
}
uint64_t chrono_time() const
{
return std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
}
std::string path_;
uint64_t expiration_seconds_;
session::ExpirationTracker expirations_;
};
} // namespace crow

View File

@@ -0,0 +1,25 @@
#pragma once
#include "crow/http_request.h"
#include "crow/http_response.h"
namespace crow
{
struct UTF8
{
struct context
{};
void before_handle(request& /*req*/, response& /*res*/, context& /*ctx*/)
{}
void after_handle(request& /*req*/, response& res, context& /*ctx*/)
{
if (get_header_value(res.headers, "Content-Type").empty())
{
res.set_header("Content-Type", "text/plain; charset=utf-8");
}
}
};
} // namespace crow