mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-27 12:31:28 +00:00
Resolve #2369
This commit is contained in:
1 parent
17f301877f
commit
43cf1822c6
4 files changed
+272
No files matched your search
@@ -41,6 +41,7 @@ test/test_no_tls
|
|||||||
test/server_fuzzer
|
test/server_fuzzer
|
||||||
test/test_proxy
|
test/test_proxy
|
||||||
test/test_proxy_mbedtls
|
test/test_proxy_mbedtls
|
||||||
|
test/test_proxy_wolfssl
|
||||||
test/test_split
|
test/test_split
|
||||||
test/test_split_mbedtls
|
test/test_split_mbedtls
|
||||||
test/test_split_wolfssl
|
test/test_split_wolfssl
|
||||||
|
|||||||
@@ -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
|
### Form data handling
|
||||||
|
|
||||||
#### URL-encoded form data ('application/x-www-form-urlencoded')
|
#### URL-encoded form data ('application/x-www-form-urlencoded')
|
||||||
|
|||||||
@@ -352,6 +352,9 @@ using socket_t = int;
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#if __cplusplus >= 201703L
|
||||||
|
#include <any>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) || \
|
#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) || \
|
||||||
defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
|
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 DownloadProgress = std::function<bool(size_t current, size_t total)>;
|
||||||
using UploadProgress = 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;
|
struct Response;
|
||||||
using ResponseHandler = std::function<bool(const Response &response)>;
|
using ResponseHandler = std::function<bool(const Response &response)>;
|
||||||
|
|
||||||
@@ -1074,6 +1206,10 @@ struct Response {
|
|||||||
std::string body;
|
std::string body;
|
||||||
std::string location; // Redirect location
|
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;
|
bool has_header(const std::string &key) const;
|
||||||
std::string get_header_value(const std::string &key, const char *def = "",
|
std::string get_header_value(const std::string &key, const char *def = "",
|
||||||
size_t id = 0) const;
|
size_t id = 0) const;
|
||||||
|
|||||||
+107
@@ -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) {
|
TEST(InvalidFormatTest, StatusCode) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user