yhirose 1 lună în urmă
părinte
comite
43cf1822c6
4 a modificat fișierele cu 272 adăugiri și 0 ștergeri
  1. 1 0
      .gitignore
  2. 28 0
      README.md
  3. 136 0
      httplib.h
  4. 107 0
      test/test.cc

+ 1 - 0
.gitignore

@@ -41,6 +41,7 @@ test/test_no_tls
 test/server_fuzzer
 test/test_proxy
 test/test_proxy_mbedtls
+test/test_proxy_wolfssl
 test/test_split
 test/test_split_mbedtls
 test/test_split_wolfssl

+ 28 - 0
README.md

@@ -468,6 +468,34 @@ 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.
+
+```cpp
+struct AuthContext {
+  std::string user_id;
+  std::string role;
+};
+
+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)};
+  return Server::HandlerResponse::Unhandled;
+});
+
+svr.Get("/me", [](const auto& /*req*/, auto& res) {
+  auto* ctx = httplib::any_cast<AuthContext>(&res.user_data["auth"]);
+  if (!ctx) {
+    res.status = StatusCode::Unauthorized_401;
+    return;
+  }
+  res.set_content("Hello " + ctx->user_id, "text/plain");
+});
+```
+
+`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')

+ 136 - 0
httplib.h

@@ -352,6 +352,9 @@ using socket_t = int;
 #include <unordered_map>
 #include <unordered_set>
 #include <utility>
+#if __cplusplus >= 201703L
+#include <any>
+#endif
 
 #if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) ||                        \
     defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
@@ -775,6 +778,135 @@ 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"; }
+};
+
+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;
+}
+
+struct any_storage {
+  virtual ~any_storage() = default;
+  virtual std::unique_ptr<any_storage> clone() const = 0;
+  virtual any_type_id type_id() const noexcept = 0;
+};
+
+template <typename T> struct any_value final : any_storage {
+  T value;
+  template <typename U> explicit any_value(U &&v) : value(std::forward<U>(v)) {}
+  std::unique_ptr<any_storage> clone() const override {
+    return std::unique_ptr<any_storage>(new any_value<T>(value));
+  }
+  any_type_id type_id() const noexcept override { return any_typeid<T>(); }
+};
+
+} // namespace detail
+
+class any {
+  std::unique_ptr<detail::any_storage> storage_;
+
+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;
+
+  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)));
+    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> 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);
+  if (!p) { throw bad_any_cast{}; }
+  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);
+  if (!p) { throw bad_any_cast{}; }
+  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);
+  if (!p) { throw bad_any_cast{}; }
+  return static_cast<T>(std::move(*p));
+}
+
+#endif // __cplusplus >= 201703L
+
 struct Response;
 using ResponseHandler = std::function<bool(const Response &response)>;
 
@@ -1074,6 +1206,10 @@ struct Response {
   std::string body;
   std::string location; // Redirect location
 
+  // 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;
+
   bool has_header(const std::string &key) const;
   std::string get_header_value(const std::string &key, const char *def = "",
                                size_t id = 0) const;

+ 107 - 0
test/test.cc

@@ -3000,6 +3000,113 @@ 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);
+  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
+  EXPECT_THROW(httplib::any_cast<std::string>(b), httplib::bad_any_cast);
+
+  // 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());
+}
+
+TEST(RequestHandlerTest, ResponseUserDataInPreRouting) {
+  struct AuthCtx {
+    std::string user_id;
+  };
+
+  Server svr;
+
+  svr.set_pre_routing_handler([](const Request & /*req*/, Response &res) {
+    res.user_data["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"]);
+    ASSERT_NE(nullptr, ctx);
+    res.set_content("Hello " + ctx->user_id, "text/plain");
+  });
+
+  auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    thread.join();
+    ASSERT_FALSE(svr.is_running());
+  });
+
+  svr.wait_until_ready();
+
+  Client cli(HOST, PORT);
+  auto res = cli.Get("/me");
+  ASSERT_TRUE(res);
+  EXPECT_EQ(StatusCode::OK_200, res->status);
+  EXPECT_EQ("Hello alice", res->body);
+}
+
+TEST(RequestHandlerTest, ResponseUserDataInPreRequest) {
+  struct RoleCtx {
+    std::string role;
+  };
+
+  Server svr;
+
+  svr.set_pre_request_handler([](const Request & /*req*/, Response &res) {
+    res.user_data["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"]);
+    ASSERT_NE(nullptr, ctx);
+    res.set_content(ctx->role, "text/plain");
+  });
+
+  auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    thread.join();
+    ASSERT_FALSE(svr.is_running());
+  });
+
+  svr.wait_until_ready();
+
+  Client cli(HOST, PORT);
+  auto res = cli.Get("/role");
+  ASSERT_TRUE(res);
+  EXPECT_EQ(StatusCode::OK_200, res->status);
+  EXPECT_EQ("admin", res->body);
+}
+
 TEST(InvalidFormatTest, StatusCode) {
   Server svr;