mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Fix #2416
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -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
@@ -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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user