mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79d83feb18 | |||
| fe56a07da5 | |||
| 907257f51d | |||
| 0c2f535b74 | |||
| c7ba963a17 | |||
| 4465e81b9f | |||
| 44215e23e9 | |||
| 91219d4508 | |||
| c86c192f3e |
@@ -4,7 +4,7 @@ langs = ["en", "ja"]
|
||||
|
||||
[site]
|
||||
title = "cpp-httplib"
|
||||
version = "0.46.0"
|
||||
version = "0.46.1"
|
||||
hostname = "https://yhirose.github.io"
|
||||
base_path = "/cpp-httplib"
|
||||
footer_message = "© 2026 Yuji Hirose. All rights reserved."
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.46.0"
|
||||
#define CPPHTTPLIB_VERSION_NUM "0x002e00"
|
||||
#define CPPHTTPLIB_VERSION "0.46.1"
|
||||
#define CPPHTTPLIB_VERSION_NUM "0x002e01"
|
||||
|
||||
#ifdef _WIN32
|
||||
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
|
||||
@@ -1643,6 +1643,8 @@ public:
|
||||
using Expect100ContinueHandler =
|
||||
std::function<int(const Request &, Response &)>;
|
||||
|
||||
using StartHandler = std::function<void()>;
|
||||
|
||||
using WebSocketHandler =
|
||||
std::function<void(const Request &, ws::WebSocket &)>;
|
||||
using SubProtocolSelector =
|
||||
@@ -1694,6 +1696,9 @@ public:
|
||||
Server &set_pre_request_handler(HandlerWithResponse handler);
|
||||
|
||||
Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);
|
||||
|
||||
Server &set_start_handler(StartHandler handler);
|
||||
|
||||
Server &set_logger(Logger logger);
|
||||
Server &set_pre_compression_logger(Logger logger);
|
||||
Server &set_error_logger(ErrorLogger error_logger);
|
||||
@@ -1883,6 +1888,7 @@ private:
|
||||
Handler post_routing_handler_;
|
||||
HandlerWithResponse pre_request_handler_;
|
||||
Expect100ContinueHandler expect_100_continue_handler_;
|
||||
StartHandler start_handler_;
|
||||
|
||||
mutable std::mutex logger_mutex_;
|
||||
Logger logger_;
|
||||
@@ -3842,6 +3848,7 @@ public:
|
||||
void set_socket_options(SocketOptions socket_options);
|
||||
void set_connection_timeout(time_t sec, time_t usec = 0);
|
||||
void set_interface(const std::string &intf);
|
||||
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
|
||||
|
||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||
void set_ca_cert_path(const std::string &path);
|
||||
@@ -3876,6 +3883,9 @@ private:
|
||||
time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
|
||||
std::string interface_;
|
||||
|
||||
// Hostname-IP map
|
||||
std::map<std::string, std::string> addr_map_;
|
||||
|
||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||
bool is_ssl_ = false;
|
||||
tls::ctx_t tls_ctx_ = nullptr;
|
||||
@@ -4629,7 +4639,7 @@ inline std::string sha1(const std::string &input) {
|
||||
// Pre-processing: adding padding bits
|
||||
std::string msg = input;
|
||||
uint64_t original_bit_len = static_cast<uint64_t>(msg.size()) * 8;
|
||||
msg.push_back(static_cast<char>(0x80));
|
||||
msg.push_back(static_cast<char>(0x80u));
|
||||
while (msg.size() % 64 != 56) {
|
||||
msg.push_back(0);
|
||||
}
|
||||
@@ -5730,7 +5740,7 @@ inline int getaddrinfo_with_timeout(const char *node, const char *service,
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows-specific implementation using GetAddrInfoEx with overlapped I/O
|
||||
OVERLAPPED overlapped = {0};
|
||||
OVERLAPPED overlapped = {};
|
||||
HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (!event) { return EAI_FAIL; }
|
||||
|
||||
@@ -5739,7 +5749,7 @@ inline int getaddrinfo_with_timeout(const char *node, const char *service,
|
||||
PADDRINFOEXW result_addrinfo = nullptr;
|
||||
HANDLE cancel_handle = nullptr;
|
||||
|
||||
ADDRINFOEXW hints_ex = {0};
|
||||
ADDRINFOEXW hints_ex = {};
|
||||
if (hints) {
|
||||
hints_ex.ai_flags = hints->ai_flags;
|
||||
hints_ex.ai_family = hints->ai_family;
|
||||
@@ -8443,6 +8453,14 @@ inline void coalesce_ranges(Ranges &ranges, size_t content_length) {
|
||||
|
||||
inline bool range_error(Request &req, Response &res) {
|
||||
if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
|
||||
if (res.body.empty() && res.content_provider_ && res.content_length_ == 0) {
|
||||
req.ranges.clear();
|
||||
if (res.status == StatusCode::PartialContent_206) {
|
||||
res.status = StatusCode::OK_200;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t content_len = static_cast<ssize_t>(
|
||||
res.content_length_ ? res.content_length_ : res.body.size());
|
||||
|
||||
@@ -11092,6 +11110,11 @@ Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_start_handler(StartHandler handler) {
|
||||
start_handler_ = std::move(handler);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_address_family(int family) {
|
||||
address_family_ = family;
|
||||
return *this;
|
||||
@@ -11787,6 +11810,8 @@ inline bool Server::listen_internal() {
|
||||
is_running_ = true;
|
||||
auto se = detail::scope_exit([&]() { is_running_ = false; });
|
||||
|
||||
if (start_handler_) { start_handler_(); }
|
||||
|
||||
{
|
||||
std::unique_ptr<TaskQueue> task_queue(new_task_queue());
|
||||
|
||||
@@ -13810,13 +13835,28 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
}
|
||||
#endif
|
||||
|
||||
// Handle Expect: 100-continue with timeout
|
||||
if (expect_100_continue && CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND > 0) {
|
||||
time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000;
|
||||
time_t usec = (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000) * 1000;
|
||||
auto ret = detail::select_read(strm.socket(), sec, usec);
|
||||
if (ret <= 0) {
|
||||
// Timeout or error: send body anyway (server didn't respond in time)
|
||||
// Handle Expect: 100-continue.
|
||||
//
|
||||
// Wait for an interim/early response by attempting to read the status line
|
||||
// under a short timeout, instead of trusting raw socket readability. Over
|
||||
// TLS, post-handshake records (e.g. session tickets) make the socket
|
||||
// readable without any HTTP response being available; relying on
|
||||
// `select_read` there caused the body to be withheld forever and the
|
||||
// request to fail with `Read` (#2458). If no status line arrives within the
|
||||
// timeout, send the body anyway (matching curl's behavior).
|
||||
auto status_line_read = false;
|
||||
if (expect_100_continue && write_request_success) {
|
||||
if (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND > 0) {
|
||||
time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000;
|
||||
time_t usec = (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000) * 1000;
|
||||
strm.set_read_timeout(sec, usec);
|
||||
status_line_read = read_response_line(strm, req, res, false);
|
||||
strm.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
|
||||
}
|
||||
|
||||
if (!status_line_read) {
|
||||
// No interim response within the timeout: send the body and handle the
|
||||
// response as usual.
|
||||
if (!write_request_body(strm, req, error)) { return false; }
|
||||
expect_100_continue = false; // Switch to normal response handling
|
||||
}
|
||||
@@ -13824,7 +13864,8 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
|
||||
// Receive response and headers
|
||||
// When using Expect: 100-continue, don't auto-skip `100 Continue` response
|
||||
if (!read_response_line(strm, req, res, !expect_100_continue) ||
|
||||
if ((!status_line_read &&
|
||||
!read_response_line(strm, req, res, !expect_100_continue)) ||
|
||||
!detail::read_headers(strm, res.headers)) {
|
||||
if (write_request_success) { error = Error::Read; }
|
||||
output_error_log(error, &req);
|
||||
@@ -20225,6 +20266,7 @@ inline WebSocketClient::WebSocketClient(
|
||||
if (!uc.port.empty() && !detail::parse_port(uc.port, port_)) { return; }
|
||||
|
||||
path_ = std::move(uc.path);
|
||||
if (!uc.query.empty()) { path_ += uc.query; }
|
||||
|
||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||
is_ssl_ = is_ssl;
|
||||
@@ -20289,9 +20331,14 @@ inline bool WebSocketClient::connect() {
|
||||
if (!is_valid_) { return false; }
|
||||
shutdown_and_close();
|
||||
|
||||
// Check is custom IP specified for host_
|
||||
std::string ip;
|
||||
auto it = addr_map_.find(host_);
|
||||
if (it != addr_map_.end()) { ip = it->second; }
|
||||
|
||||
Error error;
|
||||
sock_ = detail::create_client_socket(
|
||||
host_, std::string(), port_, address_family_, tcp_nodelay_, ipv6_v6only_,
|
||||
host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
|
||||
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_, interface_, error);
|
||||
@@ -20386,6 +20433,11 @@ inline void WebSocketClient::set_interface(const std::string &intf) {
|
||||
interface_ = intf;
|
||||
}
|
||||
|
||||
inline void WebSocketClient::set_hostname_addr_map(
|
||||
std::map<std::string, std::string> addr_map) {
|
||||
addr_map_ = std::move(addr_map);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||
|
||||
inline void WebSocketClient::set_ca_cert_path(const std::string &path) {
|
||||
|
||||
+18
-3
@@ -62,7 +62,7 @@ HEAD_SHORT=$(git rev-parse --short HEAD)
|
||||
echo " Latest commit: $HEAD_SHORT"
|
||||
|
||||
# Fetch all workflow runs for the HEAD commit
|
||||
RUNS=$(gh run list --commit "$HEAD_SHA" --json name,conclusion,headSha)
|
||||
RUNS=$(gh run list --commit "$HEAD_SHA" --json name,status,conclusion,headSha)
|
||||
|
||||
NUM_RUNS=$(echo "$RUNS" | jq 'length')
|
||||
|
||||
@@ -75,8 +75,17 @@ fi
|
||||
echo " Found $NUM_RUNS workflow run(s):"
|
||||
|
||||
FAILED=0
|
||||
RUNNING=0
|
||||
ABIDIFF_PASSED=0
|
||||
while IFS=$'\t' read -r name conclusion; do
|
||||
while IFS=$'\t' read -r name status conclusion; do
|
||||
# A run that hasn't completed yet has an empty conclusion; don't treat it
|
||||
# as a failure — the release should wait until CI finishes.
|
||||
if [ "$status" != "completed" ]; then
|
||||
echo " [ .. ] $name (still running)"
|
||||
RUNNING=1
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$name" == *abidiff* ]] || [[ "$name" == *abi* && "$name" != *stability* ]]; then
|
||||
if [ "$conclusion" = "success" ]; then
|
||||
echo " [ OK ] $name"
|
||||
@@ -94,7 +103,13 @@ while IFS=$'\t' read -r name conclusion; do
|
||||
echo " [FAIL] $name ($conclusion)"
|
||||
FAILED=1
|
||||
fi
|
||||
done < <(echo "$RUNS" | jq -r '.[] | [.name, .conclusion] | @tsv')
|
||||
done < <(echo "$RUNS" | jq -r '.[] | [.name, .status, .conclusion] | @tsv')
|
||||
|
||||
if [ "$RUNNING" -eq 1 ]; then
|
||||
echo ""
|
||||
echo "Error: Some CI checks are still running. Wait for them to complete before releasing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$FAILED" -eq 1 ]; then
|
||||
echo ""
|
||||
|
||||
+262
@@ -810,6 +810,41 @@ TEST(ParseAcceptHeaderTest, ContentTypesPopulatedAndInvalidHeaderHandling) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ServerStartHandlerTest, CalledOnceWhenReady) {
|
||||
Server svr;
|
||||
svr.Get("/", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
std::atomic<int> start_count{0};
|
||||
std::atomic<bool> running_when_called{false};
|
||||
svr.set_start_handler([&]() {
|
||||
running_when_called = svr.is_running();
|
||||
start_count++;
|
||||
});
|
||||
|
||||
auto port = svr.bind_to_any_port(HOST);
|
||||
std::thread t([&]() { svr.listen_after_bind(); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
// A successful request proves the accept loop is running, which the start
|
||||
// handler precedes; so by now the handler must have run exactly once.
|
||||
Client cli(HOST, port);
|
||||
cli.set_connection_timeout(std::chrono::seconds(5));
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
|
||||
EXPECT_EQ(1, start_count.load());
|
||||
EXPECT_TRUE(running_when_called.load());
|
||||
}
|
||||
|
||||
TEST(DivideTest, DivideStringTests) {
|
||||
auto divide = [](const std::string &str, char d) {
|
||||
std::string lhs;
|
||||
@@ -3718,6 +3753,23 @@ protected:
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.Get("/streamed-without-length",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
auto data = new std::string("abcdefg");
|
||||
res.set_content_provider(
|
||||
"text/plain",
|
||||
[data](size_t offset, DataSink &sink) {
|
||||
if (offset < data->size()) {
|
||||
sink.os << data->substr(offset);
|
||||
}
|
||||
sink.done();
|
||||
return true;
|
||||
},
|
||||
[data](bool success) {
|
||||
EXPECT_TRUE(success);
|
||||
delete data;
|
||||
});
|
||||
})
|
||||
.Get("/streamed-with-range",
|
||||
[&](const Request &req, Response &res) {
|
||||
auto data = new std::string("abcdefg");
|
||||
@@ -5197,6 +5249,16 @@ TEST_F(ServerTest, GetStreamed) {
|
||||
EXPECT_EQ(std::string("aaabbb"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedWithoutLengthWithRange) {
|
||||
auto res =
|
||||
cli_.Get("/streamed-without-length", {make_range_header({{0, -1}})});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ(false, res->has_header("Content-Length"));
|
||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(std::string("abcdefg"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedWithRange1) {
|
||||
auto res = cli_.Get("/streamed-with-range", {{make_range_header({{3, 5}})}});
|
||||
ASSERT_TRUE(res);
|
||||
@@ -13675,6 +13737,110 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(_WIN32)
|
||||
// Regression test for #2458.
|
||||
//
|
||||
// A large request body (>= CPPHTTPLIB_EXPECT_100_THRESHOLD) makes the client
|
||||
// auto-add `Expect: 100-continue`. Over TLS, the server's TLS 1.3 session
|
||||
// ticket can make the client socket spuriously readable during the
|
||||
// 100-continue wait. If the readiness is mistaken for an incoming response,
|
||||
// the client withholds the body and then blocks reading a response that never
|
||||
// comes, failing with `Failed to read connection`.
|
||||
//
|
||||
// A correct client (like curl) sends the body once no `100 Continue` arrives
|
||||
// within the timeout. This raw OpenSSL server deliberately never sends
|
||||
// `100 Continue`; the client must still deliver the body and receive 200.
|
||||
TEST(Expect100ContinueTest, TLSServerOmits100Continue) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
const auto port = PORT + 4;
|
||||
|
||||
auto srv = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
ASSERT_NE(srv, INVALID_SOCKET);
|
||||
|
||||
int opt = 1;
|
||||
::setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(static_cast<uint16_t>(port));
|
||||
::inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
|
||||
|
||||
ASSERT_EQ(0, ::bind(srv, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
|
||||
ASSERT_EQ(0, ::listen(srv, 1));
|
||||
|
||||
std::atomic<size_t> server_body_bytes{0};
|
||||
|
||||
auto server_thread = std::thread([&] {
|
||||
sockaddr_in cli_addr{};
|
||||
socklen_t cli_len = sizeof(cli_addr);
|
||||
auto cli = ::accept(srv, reinterpret_cast<sockaddr *>(&cli_addr), &cli_len);
|
||||
if (cli == INVALID_SOCKET) { return; }
|
||||
|
||||
// Bound the lifetime of the server side so a buggy client (which never
|
||||
// sends the body) cannot make this thread block forever on join.
|
||||
detail::set_socket_opt_time(cli, SOL_SOCKET, SO_RCVTIMEO, 4, 0);
|
||||
|
||||
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
|
||||
SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION);
|
||||
SSL_CTX_use_certificate_file(ctx, SERVER_CERT_FILE, SSL_FILETYPE_PEM);
|
||||
SSL_CTX_use_PrivateKey_file(ctx, SERVER_PRIVATE_KEY_FILE, SSL_FILETYPE_PEM);
|
||||
|
||||
SSL *ssl = SSL_new(ctx);
|
||||
SSL_set_fd(ssl, static_cast<int>(cli));
|
||||
|
||||
if (SSL_accept(ssl) > 0) {
|
||||
// Read the request headers. Reading here also flushes the TLS 1.3
|
||||
// session tickets to the client. Deliberately do NOT send
|
||||
// `100 Continue`.
|
||||
std::string buf;
|
||||
char tmp[1024];
|
||||
while (buf.find("\r\n\r\n") == std::string::npos) {
|
||||
auto n = SSL_read(ssl, tmp, sizeof(tmp));
|
||||
if (n <= 0) { break; }
|
||||
buf.append(tmp, static_cast<size_t>(n));
|
||||
}
|
||||
|
||||
// A correct client sends the body now; count what arrives.
|
||||
auto pos = buf.find("\r\n\r\n");
|
||||
size_t body = (pos == std::string::npos) ? 0 : buf.size() - (pos + 4);
|
||||
while (body < 4096) {
|
||||
auto n = SSL_read(ssl, tmp, sizeof(tmp));
|
||||
if (n <= 0) { break; }
|
||||
body += static_cast<size_t>(n);
|
||||
}
|
||||
server_body_bytes = body;
|
||||
|
||||
std::string resp = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok";
|
||||
SSL_write(ssl, resp.data(), static_cast<int>(resp.size()));
|
||||
SSL_shutdown(ssl);
|
||||
}
|
||||
|
||||
SSL_free(ssl);
|
||||
detail::close_socket(cli);
|
||||
SSL_CTX_free(ctx);
|
||||
});
|
||||
|
||||
auto se = detail::scope_exit([&] {
|
||||
server_thread.join();
|
||||
detail::close_socket(srv);
|
||||
});
|
||||
|
||||
SSLClient cli("127.0.0.1", port);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_connection_timeout(5, 0);
|
||||
cli.set_read_timeout(3, 0); // short, so a hang surfaces quickly
|
||||
|
||||
// Body larger than CPPHTTPLIB_EXPECT_100_THRESHOLD (1024) -> auto Expect.
|
||||
std::string body(4096, 'A');
|
||||
auto res = cli.Put("/api/test", body, "application/json");
|
||||
|
||||
ASSERT_TRUE(res) << "request failed: " << to_string(res.error());
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ(body.size(), server_body_bytes.load());
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename S, typename C>
|
||||
inline void max_timeout_test(S &svr, C &cli, time_t timeout, time_t threshold) {
|
||||
svr.Get("/stream", [&](const Request &, Response &res) {
|
||||
@@ -17540,6 +17706,57 @@ TEST(WebSocketTest, ComplexPath) {
|
||||
EXPECT_TRUE(ws2.is_valid());
|
||||
}
|
||||
|
||||
TEST(WebSocketTest, SpecifyServerIPAddress_AnotherHostname) {
|
||||
Server svr;
|
||||
svr.WebSocket("/ws", [](const Request &, ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {}
|
||||
});
|
||||
|
||||
auto port = svr.bind_to_any_port(HOST);
|
||||
std::thread t([&]() { svr.listen_after_bind(); });
|
||||
svr.wait_until_ready();
|
||||
|
||||
auto another_host = "example.com";
|
||||
auto wrong_ip = "192.0.2.1";
|
||||
|
||||
ws::WebSocketClient client("ws://localhost:" + std::to_string(port) + "/ws");
|
||||
client.set_hostname_addr_map({{another_host, wrong_ip}});
|
||||
|
||||
ASSERT_TRUE(client.connect());
|
||||
EXPECT_TRUE(client.is_open());
|
||||
client.close();
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(WebSocketTest, SpecifyServerIPAddress_RealHostname) {
|
||||
Server svr;
|
||||
svr.WebSocket("/ws", [](const Request &, ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {}
|
||||
});
|
||||
|
||||
auto port = svr.bind_to_any_port(HOST);
|
||||
std::thread t([&]() { svr.listen_after_bind(); });
|
||||
svr.wait_until_ready();
|
||||
|
||||
auto wrong_ip = "192.0.2.1";
|
||||
|
||||
ws::WebSocketClient client("ws://localhost:" + std::to_string(port) + "/ws");
|
||||
client.set_hostname_addr_map({{"localhost", wrong_ip}});
|
||||
client.set_connection_timeout(1);
|
||||
client.set_read_timeout(1);
|
||||
client.set_write_timeout(1);
|
||||
|
||||
EXPECT_FALSE(client.connect());
|
||||
EXPECT_FALSE(client.is_open());
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
class WebSocketIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
@@ -17974,6 +18191,51 @@ TEST(WebSocketPreRoutingTest, RejectWithoutAuth) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(WebSocketTest, QueryStringInHandshake) {
|
||||
Server svr;
|
||||
|
||||
std::mutex mtx;
|
||||
std::string received_target;
|
||||
std::string received_token;
|
||||
|
||||
svr.WebSocket("/ws", [&](const Request &req, ws::WebSocket &ws) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
received_target = req.target;
|
||||
if (req.has_param("token")) {
|
||||
received_token = req.get_param_value("token");
|
||||
}
|
||||
}
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
auto port = svr.bind_to_any_port("localhost");
|
||||
std::thread t([&]() { svr.listen_after_bind(); });
|
||||
svr.wait_until_ready();
|
||||
|
||||
ws::WebSocketClient client("ws://localhost:" + std::to_string(port) +
|
||||
"/ws?token=ABC&session=123");
|
||||
ASSERT_TRUE(client.connect());
|
||||
// Round-trip ensures the handler has run and captured the request.
|
||||
ASSERT_TRUE(client.send("hello"));
|
||||
std::string msg;
|
||||
ASSERT_TRUE(client.read(msg));
|
||||
EXPECT_EQ("hello", msg);
|
||||
client.close();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
EXPECT_EQ("/ws?token=ABC&session=123", received_target);
|
||||
EXPECT_EQ("ABC", received_token);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
class WebSocketSSLIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
|
||||
Reference in New Issue
Block a user