This commit is contained in:
yhirose
2026-04-03 18:27:12 -04:00
parent 6607a6a592
commit 3093bdd9ab
5 changed files with 90 additions and 156 deletions
+3 -5
View File
@@ -461,7 +461,7 @@ svr.set_pre_request_handler([](const auto& req, auto& res) {
### Response user data
`res.user_data` is a `std::map<std::string, httplib::any>` that lets pre-routing or pre-request handlers pass arbitrary data to route handlers.
`res.user_data` is a type-safe key-value store that lets pre-routing or pre-request handlers pass arbitrary data to route handlers.
```cpp
struct AuthContext {
@@ -471,12 +471,12 @@ struct AuthContext {
svr.set_pre_routing_handler([](const auto& req, auto& res) {
auto token = req.get_header_value("Authorization");
res.user_data["auth"] = AuthContext{decode_token(token)};
res.user_data.set("auth", AuthContext{decode_token(token)});
return Server::HandlerResponse::Unhandled;
});
svr.Get("/me", [](const auto& /*req*/, auto& res) {
auto* ctx = httplib::any_cast<AuthContext>(&res.user_data["auth"]);
auto* ctx = res.user_data.get<AuthContext>("auth");
if (!ctx) {
res.status = StatusCode::Unauthorized_401;
return;
@@ -485,8 +485,6 @@ svr.Get("/me", [](const auto& /*req*/, auto& res) {
});
```
`httplib::any` mirrors the C++17 `std::any` API. On C++17 and later it is an alias for `std::any`; on C++11/14 a compatible implementation is provided.
### Form data handling
#### URL-encoded form data ('application/x-www-form-urlencoded')
+3 -3
View File
@@ -164,13 +164,13 @@ Use `res.user_data` to pass data from middleware to handlers. This is useful for
```cpp
svr.set_pre_routing_handler([](const auto &req, auto &res) {
res.user_data["auth_user"] = std::string("alice");
res.user_data.set("auth_user", std::string("alice"));
return httplib::Server::HandlerResponse::Unhandled;
});
svr.Get("/me", [](const auto &req, auto &res) {
auto user = std::any_cast<std::string>(res.user_data.at("auth_user"));
res.set_content("Hello, " + user, "text/plain");
auto *user = res.user_data.get<std::string>("auth_user");
res.set_content("Hello, " + *user, "text/plain");
});
```
+3 -3
View File
@@ -164,13 +164,13 @@ svr.set_post_routing_handler([](const auto &req, auto &res) {
```cpp
svr.set_pre_routing_handler([](const auto &req, auto &res) {
res.user_data["auth_user"] = std::string("alice");
res.user_data.set("auth_user", std::string("alice"));
return httplib::Server::HandlerResponse::Unhandled;
});
svr.Get("/me", [](const auto &req, auto &res) {
auto user = std::any_cast<std::string>(res.user_data.at("auth_user"));
res.set_content("Hello, " + user, "text/plain");
auto *user = res.user_data.get<std::string>("auth_user");
res.set_content("Hello, " + *user, "text/plain");
});
```
+51 -110
View File
@@ -333,9 +333,6 @@ using socket_t = int;
#include <unordered_map>
#include <unordered_set>
#include <utility>
#if __cplusplus >= 201703L
#include <any>
#endif
// On macOS with a TLS backend, enable Keychain root certificates by default
// unless the user explicitly opts out.
@@ -797,42 +794,15 @@ using Match = std::smatch;
using DownloadProgress = std::function<bool(size_t current, size_t total)>;
using UploadProgress = std::function<bool(size_t current, size_t total)>;
// ----------------------------------------------------------------------------
// httplib::any — type-erased value container (C++11 compatible)
// On C++17+ builds, thin wrappers around std::any are provided.
// ----------------------------------------------------------------------------
#if __cplusplus >= 201703L
using any = std::any;
using bad_any_cast = std::bad_any_cast;
template <typename T> T any_cast(const any &a) { return std::any_cast<T>(a); }
template <typename T> T any_cast(any &a) { return std::any_cast<T>(a); }
template <typename T> T any_cast(any &&a) {
return std::any_cast<T>(std::move(a));
}
template <typename T> const T *any_cast(const any *a) noexcept {
return std::any_cast<T>(a);
}
template <typename T> T *any_cast(any *a) noexcept {
return std::any_cast<T>(a);
}
#else // C++11/14 implementation
class bad_any_cast : public std::bad_cast {
public:
const char *what() const noexcept override { return "bad any_cast"; }
};
/*
* detail: type-erased storage used by UserData.
* ABI-stable regardless of C++ standard always uses this custom
* implementation instead of std::any.
*/
namespace detail {
using any_type_id = const void *;
// Returns a unique per-type ID without RTTI.
// The static address is stable across TUs because function templates are
// implicitly inline and the ODR merges their statics into one.
template <typename T> any_type_id any_typeid() noexcept {
static const char id = 0;
return &id;
@@ -855,89 +825,60 @@ template <typename T> struct any_value final : any_storage {
} // namespace detail
class any {
std::unique_ptr<detail::any_storage> storage_;
class UserData {
public:
any() noexcept = default;
any(const any &o) : storage_(o.storage_ ? o.storage_->clone() : nullptr) {}
any(any &&) noexcept = default;
any &operator=(const any &o) {
storage_ = o.storage_ ? o.storage_->clone() : nullptr;
return *this;
UserData() = default;
UserData(UserData &&) noexcept = default;
UserData &operator=(UserData &&) noexcept = default;
UserData(const UserData &o) {
for (const auto &e : o.entries_) {
if (e.second) { entries_[e.first] = e.second->clone(); }
}
}
any &operator=(any &&) noexcept = default;
template <
typename T, typename D = typename std::decay<T>::type,
typename std::enable_if<!std::is_same<D, any>::value, int>::type = 0>
any(T &&v) : storage_(new detail::any_value<D>(std::forward<T>(v))) {}
template <
typename T, typename D = typename std::decay<T>::type,
typename std::enable_if<!std::is_same<D, any>::value, int>::type = 0>
any &operator=(T &&v) {
storage_.reset(new detail::any_value<D>(std::forward<T>(v)));
UserData &operator=(const UserData &o) {
if (this != &o) {
entries_.clear();
for (const auto &e : o.entries_) {
if (e.second) { entries_[e.first] = e.second->clone(); }
}
}
return *this;
}
bool has_value() const noexcept { return storage_ != nullptr; }
void reset() noexcept { storage_.reset(); }
template <typename T> void set(const std::string &key, T &&value) {
using D = typename std::decay<T>::type;
entries_[key].reset(new detail::any_value<D>(std::forward<T>(value)));
}
template <typename T> friend T *any_cast(any *a) noexcept;
template <typename T> friend const T *any_cast(const any *a) noexcept;
template <typename T> T *get(const std::string &key) noexcept {
auto it = entries_.find(key);
if (it == entries_.end() || !it->second) { return nullptr; }
if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; }
return &static_cast<detail::any_value<T> *>(it->second.get())->value;
}
template <typename T> const T *get(const std::string &key) const noexcept {
auto it = entries_.find(key);
if (it == entries_.end() || !it->second) { return nullptr; }
if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; }
return &static_cast<const detail::any_value<T> *>(it->second.get())->value;
}
bool has(const std::string &key) const noexcept {
return entries_.find(key) != entries_.end();
}
void erase(const std::string &key) { entries_.erase(key); }
void clear() noexcept { entries_.clear(); }
private:
std::unordered_map<std::string, std::unique_ptr<detail::any_storage>>
entries_;
};
template <typename T> T *any_cast(any *a) noexcept {
if (!a || !a->storage_) { return nullptr; }
if (a->storage_->type_id() != detail::any_typeid<T>()) { return nullptr; }
return &static_cast<detail::any_value<T> *>(a->storage_.get())->value;
}
template <typename T> const T *any_cast(const any *a) noexcept {
if (!a || !a->storage_) { return nullptr; }
if (a->storage_->type_id() != detail::any_typeid<T>()) { return nullptr; }
return &static_cast<const detail::any_value<T> *>(a->storage_.get())->value;
}
template <typename T> T any_cast(const any &a) {
using U =
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
const U *p = any_cast<U>(&a);
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
if (!p) { throw bad_any_cast{}; }
#else
if (!p) { std::abort(); }
#endif
return static_cast<T>(*p);
}
template <typename T> T any_cast(any &a) {
using U =
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
U *p = any_cast<U>(&a);
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
if (!p) { throw bad_any_cast{}; }
#else
if (!p) { std::abort(); }
#endif
return static_cast<T>(*p);
}
template <typename T> T any_cast(any &&a) {
using U =
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
U *p = any_cast<U>(&a);
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
if (!p) { throw bad_any_cast{}; }
#else
if (!p) { std::abort(); }
#endif
return static_cast<T>(std::move(*p));
}
#endif // __cplusplus >= 201703L
struct Response;
using ResponseHandler = std::function<bool(const Response &response)>;
@@ -1297,7 +1238,7 @@ struct Response {
// User-defined context — set by pre-routing/pre-request handlers and read
// by route handlers to pass arbitrary data (e.g. decoded auth tokens).
std::map<std::string, any> user_data;
UserData user_data;
bool has_header(const std::string &key) const;
std::string get_header_value(const std::string &key, const char *def = "",
+30 -35
View File
@@ -3120,45 +3120,40 @@ TEST(RequestHandlerTest, PreRequestHandler) {
}
}
TEST(AnyTest, BasicOperations) {
// Default construction
httplib::any a;
EXPECT_FALSE(a.has_value());
TEST(UserDataTest, BasicOperations) {
httplib::UserData ud;
// Value construction and any_cast (pointer form, noexcept)
httplib::any b(42);
EXPECT_TRUE(b.has_value());
auto *p = httplib::any_cast<int>(&b);
// Initially empty
EXPECT_FALSE(ud.has("key"));
EXPECT_EQ(nullptr, ud.get<int>("key"));
// set and get
ud.set("key", 42);
EXPECT_TRUE(ud.has("key"));
auto *p = ud.get<int>("key");
ASSERT_NE(nullptr, p);
EXPECT_EQ(42, *p);
// Type mismatch → nullptr
auto *q = httplib::any_cast<std::string>(&b);
EXPECT_EQ(nullptr, q);
EXPECT_EQ(nullptr, ud.get<std::string>("key"));
// any_cast (value form) succeeds
EXPECT_EQ(42, httplib::any_cast<int>(b));
// Overwrite with different type
ud.set("key", std::string("hello"));
EXPECT_EQ(nullptr, ud.get<int>("key"));
auto *s = ud.get<std::string>("key");
ASSERT_NE(nullptr, s);
EXPECT_EQ("hello", *s);
// any_cast (value form) throws on type mismatch
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
EXPECT_THROW(httplib::any_cast<std::string>(b), httplib::bad_any_cast);
#endif
// erase
ud.erase("key");
EXPECT_FALSE(ud.has("key"));
// Copy
httplib::any c = b;
EXPECT_EQ(42, httplib::any_cast<int>(c));
// Move
httplib::any d = std::move(c);
EXPECT_EQ(42, httplib::any_cast<int>(d));
// Assignment with different type
b = std::string("hello");
EXPECT_EQ("hello", httplib::any_cast<std::string>(b));
// Reset
b.reset();
EXPECT_FALSE(b.has_value());
// clear
ud.set("a", 1);
ud.set("b", 2);
ud.clear();
EXPECT_FALSE(ud.has("a"));
EXPECT_FALSE(ud.has("b"));
}
TEST(RequestHandlerTest, ResponseUserDataInPreRouting) {
@@ -3169,12 +3164,12 @@ TEST(RequestHandlerTest, ResponseUserDataInPreRouting) {
Server svr;
svr.set_pre_routing_handler([](const Request & /*req*/, Response &res) {
res.user_data["auth"] = AuthCtx{"alice"};
res.user_data.set("auth", AuthCtx{"alice"});
return Server::HandlerResponse::Unhandled;
});
svr.Get("/me", [](const Request & /*req*/, Response &res) {
auto *ctx = httplib::any_cast<AuthCtx>(&res.user_data["auth"]);
auto *ctx = res.user_data.get<AuthCtx>("auth");
ASSERT_NE(nullptr, ctx);
res.set_content("Hello " + ctx->user_id, "text/plain");
});
@@ -3203,12 +3198,12 @@ TEST(RequestHandlerTest, ResponseUserDataInPreRequest) {
Server svr;
svr.set_pre_request_handler([](const Request & /*req*/, Response &res) {
res.user_data["role"] = RoleCtx{"admin"};
res.user_data.set("role", RoleCtx{"admin"});
return Server::HandlerResponse::Unhandled;
});
svr.Get("/role", [](const Request & /*req*/, Response &res) {
auto *ctx = httplib::any_cast<RoleCtx>(&res.user_data["role"]);
auto *ctx = res.user_data.get<RoleCtx>("role");
ASSERT_NE(nullptr, ctx);
res.set_content(ctx->role, "text/plain");
});