diff --git a/README.md b/README.md index ebde4b8..605f20d 100644 --- a/README.md +++ b/README.md @@ -461,7 +461,7 @@ svr.set_pre_request_handler([](const auto& req, auto& res) { ### Response user data -`res.user_data` is a `std::map` 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(&res.user_data["auth"]); + auto* ctx = res.user_data.get("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') diff --git a/docs-src/pages/en/tour/09-whats-next.md b/docs-src/pages/en/tour/09-whats-next.md index bd3cfff..815a2a7 100644 --- a/docs-src/pages/en/tour/09-whats-next.md +++ b/docs-src/pages/en/tour/09-whats-next.md @@ -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(res.user_data.at("auth_user")); - res.set_content("Hello, " + user, "text/plain"); + auto *user = res.user_data.get("auth_user"); + res.set_content("Hello, " + *user, "text/plain"); }); ``` diff --git a/docs-src/pages/ja/tour/09-whats-next.md b/docs-src/pages/ja/tour/09-whats-next.md index a55c54d..4554cd8 100644 --- a/docs-src/pages/ja/tour/09-whats-next.md +++ b/docs-src/pages/ja/tour/09-whats-next.md @@ -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(res.user_data.at("auth_user")); - res.set_content("Hello, " + user, "text/plain"); + auto *user = res.user_data.get("auth_user"); + res.set_content("Hello, " + *user, "text/plain"); }); ``` diff --git a/httplib.h b/httplib.h index 439b3fa..b23043d 100644 --- a/httplib.h +++ b/httplib.h @@ -333,9 +333,6 @@ using socket_t = int; #include #include #include -#if __cplusplus >= 201703L -#include -#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; using UploadProgress = std::function; -// ---------------------------------------------------------------------------- -// 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 T any_cast(const any &a) { return std::any_cast(a); } -template T any_cast(any &a) { return std::any_cast(a); } -template T any_cast(any &&a) { - return std::any_cast(std::move(a)); -} -template const T *any_cast(const any *a) noexcept { - return std::any_cast(a); -} -template T *any_cast(any *a) noexcept { - return std::any_cast(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 any_type_id any_typeid() noexcept { static const char id = 0; return &id; @@ -855,89 +825,60 @@ template struct any_value final : any_storage { } // namespace detail -class any { - std::unique_ptr 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::type, - typename std::enable_if::value, int>::type = 0> - any(T &&v) : storage_(new detail::any_value(std::forward(v))) {} - - template < - typename T, typename D = typename std::decay::type, - typename std::enable_if::value, int>::type = 0> - any &operator=(T &&v) { - storage_.reset(new detail::any_value(std::forward(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 void set(const std::string &key, T &&value) { + using D = typename std::decay::type; + entries_[key].reset(new detail::any_value(std::forward(value))); + } - template friend T *any_cast(any *a) noexcept; - template friend const T *any_cast(const any *a) noexcept; + template 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()) { return nullptr; } + return &static_cast *>(it->second.get())->value; + } + + template 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()) { return nullptr; } + return &static_cast *>(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> + entries_; }; -template T *any_cast(any *a) noexcept { - if (!a || !a->storage_) { return nullptr; } - if (a->storage_->type_id() != detail::any_typeid()) { return nullptr; } - return &static_cast *>(a->storage_.get())->value; -} - -template const T *any_cast(const any *a) noexcept { - if (!a || !a->storage_) { return nullptr; } - if (a->storage_->type_id() != detail::any_typeid()) { return nullptr; } - return &static_cast *>(a->storage_.get())->value; -} - -template T any_cast(const any &a) { - using U = - typename std::remove_cv::type>::type; - const U *p = any_cast(&a); -#ifndef CPPHTTPLIB_NO_EXCEPTIONS - if (!p) { throw bad_any_cast{}; } -#else - if (!p) { std::abort(); } -#endif - return static_cast(*p); -} - -template T any_cast(any &a) { - using U = - typename std::remove_cv::type>::type; - U *p = any_cast(&a); -#ifndef CPPHTTPLIB_NO_EXCEPTIONS - if (!p) { throw bad_any_cast{}; } -#else - if (!p) { std::abort(); } -#endif - return static_cast(*p); -} - -template T any_cast(any &&a) { - using U = - typename std::remove_cv::type>::type; - U *p = any_cast(&a); -#ifndef CPPHTTPLIB_NO_EXCEPTIONS - if (!p) { throw bad_any_cast{}; } -#else - if (!p) { std::abort(); } -#endif - return static_cast(std::move(*p)); -} - -#endif // __cplusplus >= 201703L - struct Response; using ResponseHandler = std::function; @@ -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 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 = "", diff --git a/test/test.cc b/test/test.cc index eb73eb1..294b982 100644 --- a/test/test.cc +++ b/test/test.cc @@ -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(&b); + // Initially empty + EXPECT_FALSE(ud.has("key")); + EXPECT_EQ(nullptr, ud.get("key")); + + // set and get + ud.set("key", 42); + EXPECT_TRUE(ud.has("key")); + auto *p = ud.get("key"); ASSERT_NE(nullptr, p); EXPECT_EQ(42, *p); // Type mismatch → nullptr - auto *q = httplib::any_cast(&b); - EXPECT_EQ(nullptr, q); + EXPECT_EQ(nullptr, ud.get("key")); - // any_cast (value form) succeeds - EXPECT_EQ(42, httplib::any_cast(b)); + // Overwrite with different type + ud.set("key", std::string("hello")); + EXPECT_EQ(nullptr, ud.get("key")); + auto *s = ud.get("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(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(c)); - - // Move - httplib::any d = std::move(c); - EXPECT_EQ(42, httplib::any_cast(d)); - - // Assignment with different type - b = std::string("hello"); - EXPECT_EQ("hello", httplib::any_cast(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(&res.user_data["auth"]); + auto *ctx = res.user_data.get("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(&res.user_data["role"]); + auto *ctx = res.user_data.get("role"); ASSERT_NE(nullptr, ctx); res.set_content(ctx->role, "text/plain"); });