yhirose 2 روز پیش
والد
کامیت
3093bdd9ab
5فایلهای تغییر یافته به همراه87 افزوده شده و 153 حذف شده
  1. 3 5
      README.md
  2. 3 3
      docs-src/pages/en/tour/09-whats-next.md
  3. 3 3
      docs-src/pages/ja/tour/09-whats-next.md
  4. 47 106
      httplib.h
  5. 31 36
      test/test.cc

+ 3 - 5
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<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
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<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
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<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");
 });
 ```
 

+ 47 - 106
httplib.h

@@ -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,88 +825,59 @@ 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;
-  }
-  any &operator=(any &&) noexcept = default;
+  UserData() = default;
+  UserData(UserData &&) noexcept = default;
+  UserData &operator=(UserData &&) 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))) {}
+  UserData(const UserData &o) {
+    for (const auto &e : o.entries_) {
+      if (e.second) { entries_[e.first] = e.second->clone(); }
+    }
+  }
 
-  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> friend T *any_cast(any *a) noexcept;
-  template <typename T> friend const T *any_cast(const any *a) noexcept;
-};
+  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> 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> 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 *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> 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;
+  }
 
-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);
-}
+  bool has(const std::string &key) const noexcept {
+    return entries_.find(key) != entries_.end();
+  }
 
-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);
-}
+  void erase(const std::string &key) { entries_.erase(key); }
 
-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));
-}
+  void clear() noexcept { entries_.clear(); }
 
-#endif // __cplusplus >= 201703L
+private:
+  std::unordered_map<std::string, std::unique_ptr<detail::any_storage>>
+      entries_;
+};
 
 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 = "",

+ 31 - 36
test/test.cc

@@ -3120,45 +3120,40 @@ TEST(RequestHandlerTest, PreRequestHandler) {
   }
 }
 
-TEST(AnyTest, BasicOperations) {
-  // Default construction
-  httplib::any a;
-  EXPECT_FALSE(a.has_value());
-
-  // Value construction and any_cast (pointer form, noexcept)
-  httplib::any b(42);
-  EXPECT_TRUE(b.has_value());
-  auto *p = httplib::any_cast<int>(&b);
+TEST(UserDataTest, BasicOperations) {
+  httplib::UserData ud;
+
+  // 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);
-
-  // any_cast (value form) succeeds
-  EXPECT_EQ(42, httplib::any_cast<int>(b));
-
-  // 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
-
-  // Copy
-  httplib::any c = b;
-  EXPECT_EQ(42, httplib::any_cast<int>(c));
+  EXPECT_EQ(nullptr, ud.get<std::string>("key"));
 
-  // Move
-  httplib::any d = std::move(c);
-  EXPECT_EQ(42, httplib::any_cast<int>(d));
+  // 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);
 
-  // Assignment with different type
-  b = std::string("hello");
-  EXPECT_EQ("hello", httplib::any_cast<std::string>(b));
+  // erase
+  ud.erase("key");
+  EXPECT_FALSE(ud.has("key"));
 
-  // 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");
   });