mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc9b223acc | |||
| ba824089d7 | |||
| 1a2faf09e0 | |||
| 5a43bb8149 | |||
| 0104614656 | |||
| 77a77f6d2d | |||
| 089b9daa1c | |||
| ba34ea4ee8 | |||
| 2917b8a005 | |||
| dcf24d45a2 | |||
| 75fdb06696 | |||
| e00ad37580 | |||
| 5cfb70c2b4 | |||
| 2a70c45697 | |||
| c58b00580e |
@@ -266,7 +266,7 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
return true; // return 'false' if you want to cancel the process.
|
||||
},
|
||||
[data] { delete data; });
|
||||
[data](bool success) { delete data; });
|
||||
});
|
||||
```
|
||||
|
||||
@@ -781,7 +781,7 @@ Note: Windows 8 or lower and Cygwin on Windows are not supported.
|
||||
License
|
||||
-------
|
||||
|
||||
MIT license (© 2020 Yuji Hirose)
|
||||
MIT license (© 2021 Yuji Hirose)
|
||||
|
||||
Special Thanks To
|
||||
-----------------
|
||||
|
||||
+2
-9
@@ -1,10 +1,3 @@
|
||||
//
|
||||
// sse.cc
|
||||
//
|
||||
// Copyright (c) 2020 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
@@ -39,8 +32,8 @@ public:
|
||||
private:
|
||||
mutex m_;
|
||||
condition_variable cv_;
|
||||
atomic_int id_ = 0;
|
||||
atomic_int cid_ = -1;
|
||||
atomic_int id_{0};
|
||||
atomic_int cid_{-1};
|
||||
string message_;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// httplib.h
|
||||
//
|
||||
// Copyright (c) 2020 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2021 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
@@ -337,6 +337,8 @@ using ContentProvider =
|
||||
using ContentProviderWithoutLength =
|
||||
std::function<bool(size_t offset, DataSink &sink)>;
|
||||
|
||||
using ContentProviderResourceReleaser = std::function<void(bool success)>;
|
||||
|
||||
using ContentReceiverWithProgress =
|
||||
std::function<bool(const char *data, size_t data_length, uint64_t offset,
|
||||
uint64_t total_length)>;
|
||||
@@ -446,15 +448,15 @@ struct Response {
|
||||
|
||||
void set_content_provider(
|
||||
size_t length, const char *content_type, ContentProvider provider,
|
||||
const std::function<void()> &resource_releaser = nullptr);
|
||||
ContentProviderResourceReleaser resource_releaser = nullptr);
|
||||
|
||||
void set_content_provider(
|
||||
const char *content_type, ContentProviderWithoutLength provider,
|
||||
const std::function<void()> &resource_releaser = nullptr);
|
||||
ContentProviderResourceReleaser resource_releaser = nullptr);
|
||||
|
||||
void set_chunked_content_provider(
|
||||
const char *content_type, ContentProviderWithoutLength provider,
|
||||
const std::function<void()> &resource_releaser = nullptr);
|
||||
ContentProviderResourceReleaser resource_releaser = nullptr);
|
||||
|
||||
Response() = default;
|
||||
Response(const Response &) = default;
|
||||
@@ -463,15 +465,16 @@ struct Response {
|
||||
Response &operator=(Response &&) = default;
|
||||
~Response() {
|
||||
if (content_provider_resource_releaser_) {
|
||||
content_provider_resource_releaser_();
|
||||
content_provider_resource_releaser_(content_provider_success_);
|
||||
}
|
||||
}
|
||||
|
||||
// private members...
|
||||
size_t content_length_ = 0;
|
||||
ContentProvider content_provider_;
|
||||
std::function<void()> content_provider_resource_releaser_;
|
||||
ContentProviderResourceReleaser content_provider_resource_releaser_;
|
||||
bool is_chunked_content_provider_ = false;
|
||||
bool content_provider_success_ = false;
|
||||
};
|
||||
|
||||
class Stream {
|
||||
@@ -667,6 +670,8 @@ public:
|
||||
Server &set_tcp_nodelay(bool on);
|
||||
Server &set_socket_options(SocketOptions socket_options);
|
||||
|
||||
Server &set_default_headers(Headers headers);
|
||||
|
||||
Server &set_keep_alive_max_count(size_t count);
|
||||
Server &set_keep_alive_timeout(time_t sec);
|
||||
|
||||
@@ -786,6 +791,8 @@ private:
|
||||
int address_family_ = AF_UNSPEC;
|
||||
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
|
||||
SocketOptions socket_options_ = default_socket_options;
|
||||
|
||||
Headers default_headers_;
|
||||
};
|
||||
|
||||
enum class Error {
|
||||
@@ -804,6 +811,11 @@ enum class Error {
|
||||
Compression,
|
||||
};
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
|
||||
os << static_cast<std::underlying_type<Error>::type>(obj);
|
||||
return os;
|
||||
}
|
||||
|
||||
class Result {
|
||||
public:
|
||||
Result(std::unique_ptr<Response> &&res, Error err,
|
||||
@@ -999,6 +1011,8 @@ public:
|
||||
void set_keep_alive(bool on);
|
||||
void set_follow_location(bool on);
|
||||
|
||||
void set_url_encode(bool on);
|
||||
|
||||
void set_compress(bool on);
|
||||
|
||||
void set_decompress(bool on);
|
||||
@@ -1043,10 +1057,6 @@ protected:
|
||||
void shutdown_socket(Socket &socket);
|
||||
void close_socket(Socket &socket);
|
||||
|
||||
// Similar to shutdown_ssl and close_socket, this should NOT be called
|
||||
// concurrently with a DIFFERENT thread sending requests from the socket
|
||||
void lock_socket_and_shutdown_and_close();
|
||||
|
||||
bool process_request(Stream &strm, Request &req, Response &res,
|
||||
bool close_connection, Error &error);
|
||||
|
||||
@@ -1095,6 +1105,8 @@ protected:
|
||||
bool keep_alive_ = false;
|
||||
bool follow_location_ = false;
|
||||
|
||||
bool url_encode_ = true;
|
||||
|
||||
int address_family_ = AF_UNSPEC;
|
||||
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
|
||||
SocketOptions socket_options_ = nullptr;
|
||||
@@ -1312,6 +1324,8 @@ public:
|
||||
void set_keep_alive(bool on);
|
||||
void set_follow_location(bool on);
|
||||
|
||||
void set_url_encode(bool on);
|
||||
|
||||
void set_compress(bool on);
|
||||
|
||||
void set_decompress(bool on);
|
||||
@@ -1401,6 +1415,7 @@ public:
|
||||
private:
|
||||
bool create_and_connect_socket(Socket &socket, Error &error) override;
|
||||
void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
|
||||
void shutdown_ssl_impl(Socket &socket, bool shutdown_socket);
|
||||
|
||||
bool process_socket(const Socket &socket,
|
||||
std::function<bool(Stream &strm)> callback) override;
|
||||
@@ -1612,6 +1627,7 @@ inline std::string encode_query_param(const std::string &value) {
|
||||
|
||||
inline std::string encode_url(const std::string &s) {
|
||||
std::string result;
|
||||
result.reserve(s.size());
|
||||
|
||||
for (size_t i = 0; s[i]; i++) {
|
||||
switch (s[i]) {
|
||||
@@ -2223,45 +2239,45 @@ inline socket_t create_client_socket(
|
||||
time_t write_timeout_usec, const std::string &intf, Error &error) {
|
||||
auto sock = create_socket(
|
||||
host, port, address_family, 0, tcp_nodelay, std::move(socket_options),
|
||||
[&](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
[&](socket_t sock2, struct addrinfo &ai) -> bool {
|
||||
if (!intf.empty()) {
|
||||
#ifdef USE_IF2IP
|
||||
auto ip = if2ip(intf);
|
||||
if (ip.empty()) { ip = intf; }
|
||||
if (!bind_ip_address(sock, ip.c_str())) {
|
||||
if (!bind_ip_address(sock2, ip.c_str())) {
|
||||
error = Error::BindIPAddress;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
set_nonblocking(sock, true);
|
||||
set_nonblocking(sock2, true);
|
||||
|
||||
auto ret =
|
||||
::connect(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
|
||||
::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
|
||||
|
||||
if (ret < 0) {
|
||||
if (is_connection_error() ||
|
||||
!wait_until_socket_is_ready(sock, connection_timeout_sec,
|
||||
!wait_until_socket_is_ready(sock2, connection_timeout_sec,
|
||||
connection_timeout_usec)) {
|
||||
error = Error::Connection;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set_nonblocking(sock, false);
|
||||
set_nonblocking(sock2, false);
|
||||
|
||||
{
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(read_timeout_sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec);
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
|
||||
setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
|
||||
}
|
||||
{
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(write_timeout_sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec);
|
||||
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
|
||||
setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
|
||||
}
|
||||
|
||||
error = Error::Success;
|
||||
@@ -2551,7 +2567,7 @@ public:
|
||||
|
||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
do {
|
||||
strm_.avail_out = buff.size();
|
||||
strm_.avail_out = static_cast<uInt>(buff.size());
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
|
||||
ret = deflate(&strm_, flush);
|
||||
@@ -2602,7 +2618,7 @@ public:
|
||||
|
||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
while (strm_.avail_in > 0) {
|
||||
strm_.avail_out = buff.size();
|
||||
strm_.avail_out = static_cast<uInt>(buff.size());
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
|
||||
ret = inflate(&strm_, Z_NO_FLUSH);
|
||||
@@ -2939,8 +2955,8 @@ bool prepare_content_receiver(T &x, int &status,
|
||||
ContentReceiverWithProgress out = [&](const char *buf, size_t n,
|
||||
uint64_t off, uint64_t len) {
|
||||
return decompressor->decompress(buf, n,
|
||||
[&](const char *buf, size_t n) {
|
||||
return receiver(buf, n, off, len);
|
||||
[&](const char *buf2, size_t n2) {
|
||||
return receiver(buf2, n2, off, len);
|
||||
});
|
||||
};
|
||||
return callback(std::move(out));
|
||||
@@ -3110,9 +3126,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
// Emit chunked response header and footer for each chunk
|
||||
auto chunk =
|
||||
from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
|
||||
if (!write_data(strm, chunk.data(), chunk.size())) {
|
||||
ok = false;
|
||||
}
|
||||
if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
@@ -4019,10 +4033,9 @@ inline void Response::set_content(const std::string &s,
|
||||
set_content(s.data(), s.size(), content_type);
|
||||
}
|
||||
|
||||
inline void
|
||||
Response::set_content_provider(size_t in_length, const char *content_type,
|
||||
ContentProvider provider,
|
||||
const std::function<void()> &resource_releaser) {
|
||||
inline void Response::set_content_provider(
|
||||
size_t in_length, const char *content_type, ContentProvider provider,
|
||||
ContentProviderResourceReleaser resource_releaser) {
|
||||
assert(in_length > 0);
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = in_length;
|
||||
@@ -4031,10 +4044,9 @@ Response::set_content_provider(size_t in_length, const char *content_type,
|
||||
is_chunked_content_provider_ = false;
|
||||
}
|
||||
|
||||
inline void
|
||||
Response::set_content_provider(const char *content_type,
|
||||
ContentProviderWithoutLength provider,
|
||||
const std::function<void()> &resource_releaser) {
|
||||
inline void Response::set_content_provider(
|
||||
const char *content_type, ContentProviderWithoutLength provider,
|
||||
ContentProviderResourceReleaser resource_releaser) {
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = 0;
|
||||
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
|
||||
@@ -4044,7 +4056,7 @@ Response::set_content_provider(const char *content_type,
|
||||
|
||||
inline void Response::set_chunked_content_provider(
|
||||
const char *content_type, ContentProviderWithoutLength provider,
|
||||
const std::function<void()> &resource_releaser) {
|
||||
ContentProviderResourceReleaser resource_releaser) {
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = 0;
|
||||
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
|
||||
@@ -4420,6 +4432,11 @@ inline Server &Server::set_socket_options(SocketOptions socket_options) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_default_headers(Headers headers) {
|
||||
default_headers_ = std::move(headers);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_keep_alive_max_count(size_t count) {
|
||||
keep_alive_max_count_ = count;
|
||||
return *this;
|
||||
@@ -4598,8 +4615,11 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
if (!res.body.empty()) {
|
||||
if (!strm.write(res.body)) { ret = false; }
|
||||
} else if (res.content_provider_) {
|
||||
if (!write_content_with_provider(strm, req, res, boundary,
|
||||
content_type)) {
|
||||
if (write_content_with_provider(strm, req, res, boundary,
|
||||
content_type)) {
|
||||
res.content_provider_success_ = true;
|
||||
} else {
|
||||
res.content_provider_success_ = false;
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
@@ -5124,6 +5144,12 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
|
||||
res.version = "HTTP/1.1";
|
||||
|
||||
for (const auto &header : default_headers_) {
|
||||
if (res.headers.find(header.first) == res.headers.end()) {
|
||||
res.headers.insert(header);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL).
|
||||
#else
|
||||
@@ -5248,7 +5274,11 @@ inline ClientImpl::ClientImpl(const std::string &host, int port,
|
||||
host_and_port_(host_ + ":" + std::to_string(port_)),
|
||||
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
||||
|
||||
inline ClientImpl::~ClientImpl() { lock_socket_and_shutdown_and_close(); }
|
||||
inline ClientImpl::~ClientImpl() {
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
inline bool ClientImpl::is_valid() const { return true; }
|
||||
|
||||
@@ -5269,6 +5299,8 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
||||
#endif
|
||||
keep_alive_ = rhs.keep_alive_;
|
||||
follow_location_ = rhs.follow_location_;
|
||||
url_encode_ = rhs.url_encode_;
|
||||
address_family_ = rhs.address_family_;
|
||||
tcp_nodelay_ = rhs.tcp_nodelay_;
|
||||
socket_options_ = rhs.socket_options_;
|
||||
compress_ = rhs.compress_;
|
||||
@@ -5344,13 +5376,6 @@ inline void ClientImpl::close_socket(Socket &socket) {
|
||||
socket.sock = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
inline void ClientImpl::lock_socket_and_shutdown_and_close() {
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_ssl(socket_, true);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
|
||||
Response &res) {
|
||||
std::array<char, 2048> buf;
|
||||
@@ -5671,7 +5696,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
}
|
||||
}
|
||||
|
||||
if (!basic_auth_password_.empty()) {
|
||||
if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) {
|
||||
req.headers.insert(make_basic_authentication_header(
|
||||
basic_auth_username_, basic_auth_password_, false));
|
||||
}
|
||||
@@ -5696,7 +5721,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
{
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
const auto &path = detail::encode_url(req.path);
|
||||
const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path;
|
||||
bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
|
||||
|
||||
detail::write_headers(bstrm, req.headers);
|
||||
@@ -5899,7 +5924,10 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
// mutex during the process. It would be a bug to call it from a different
|
||||
// thread since it's a thread-safety issue to do these things to the socket
|
||||
// if another thread is using the socket.
|
||||
lock_socket_and_shutdown_and_close();
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_ssl(socket_, true);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
// Log
|
||||
@@ -6425,6 +6453,8 @@ inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
|
||||
|
||||
inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
|
||||
|
||||
inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; }
|
||||
|
||||
inline void ClientImpl::set_default_headers(Headers headers) {
|
||||
default_headers_ = std::move(headers);
|
||||
}
|
||||
@@ -6657,7 +6687,12 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
if (ret < 0) {
|
||||
auto err = SSL_get_error(ssl_, ret);
|
||||
#ifdef _WIN32
|
||||
while (err == SSL_ERROR_WANT_READ ||
|
||||
err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT) {
|
||||
#else
|
||||
while (err == SSL_ERROR_WANT_READ) {
|
||||
#endif
|
||||
if (SSL_pending(ssl_) > 0) {
|
||||
return SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
} else if (is_readable()) {
|
||||
@@ -6845,7 +6880,7 @@ inline SSLClient::~SSLClient() {
|
||||
// Make sure to shut down SSL since shutdown_ssl will resolve to the
|
||||
// base function rather than the derived function once we get to the
|
||||
// base class destructor, and won't free the SSL (causing a leak).
|
||||
SSLClient::shutdown_ssl(socket_, true);
|
||||
shutdown_ssl_impl(socket_, true);
|
||||
}
|
||||
|
||||
inline bool SSLClient::is_valid() const { return ctx_; }
|
||||
@@ -7024,6 +7059,11 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
}
|
||||
|
||||
inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
|
||||
shutdown_ssl_impl(socket, shutdown_gracefully);
|
||||
}
|
||||
|
||||
inline void SSLClient::shutdown_ssl_impl(Socket &socket,
|
||||
bool shutdown_gracefully) {
|
||||
if (socket.sock == INVALID_SOCKET) {
|
||||
assert(socket.ssl == nullptr);
|
||||
return;
|
||||
@@ -7553,6 +7593,8 @@ inline void Client::set_follow_location(bool on) {
|
||||
cli_->set_follow_location(on);
|
||||
}
|
||||
|
||||
inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); }
|
||||
|
||||
inline void Client::set_compress(bool on) { cli_->set_compress(on); }
|
||||
|
||||
inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
project('cpp-httplib', 'cpp', license: 'MIT')
|
||||
|
||||
cpp_httplib_dep = declare_dependency(include_directories: include_directories('.'))
|
||||
|
||||
if meson.version().version_compare('>=0.54.0')
|
||||
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
|
||||
endif
|
||||
+155
-29
@@ -5,6 +5,7 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
@@ -436,26 +437,6 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver) {
|
||||
EXPECT_EQ(out, body);
|
||||
}
|
||||
|
||||
TEST(DefaultHeadersTest, FromHTTPBin) {
|
||||
Client cli("httpbin.org");
|
||||
cli.set_default_headers({make_range_header({{1, 10}})});
|
||||
cli.set_connection_timeout(5);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/range/32");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijk", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/range/32");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijk", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RangeTest, FromHTTPBin) {
|
||||
auto host = "httpbin.org";
|
||||
|
||||
@@ -547,6 +528,23 @@ TEST(ConnectionErrorTest, InvalidHost2) {
|
||||
EXPECT_EQ(Error::Connection, res.error());
|
||||
}
|
||||
|
||||
TEST(ConnectionErrorTest, InvalidHostCheckResultErrorToString) {
|
||||
auto host = "httpbin.org/";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
#else
|
||||
Client cli(host);
|
||||
#endif
|
||||
cli.set_connection_timeout(std::chrono::seconds(2));
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(!res);
|
||||
stringstream s;
|
||||
s << "error code: " << res.error();
|
||||
EXPECT_EQ("error code: 2", s.str());
|
||||
}
|
||||
|
||||
TEST(ConnectionErrorTest, InvalidPort) {
|
||||
auto host = "localhost";
|
||||
auto port = 44380;
|
||||
@@ -947,6 +945,42 @@ TEST(RedirectFromPageWithContent, Redirect) {
|
||||
|
||||
#endif
|
||||
|
||||
TEST(PathUrlEncodeTest, PathUrlEncode) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/foo", [](const Request &req, Response &res) {
|
||||
auto a = req.params.find("a");
|
||||
if (a != req.params.end()) {
|
||||
res.set_content((*a).second, "text/plain");
|
||||
res.status = 200;
|
||||
} else {
|
||||
res.status = 400;
|
||||
}
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
cli.set_url_encode(false);
|
||||
|
||||
auto res = cli.Get("/foo?a=explicitly+encoded");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
// This expects it back with a space, as the `+` won't have been
|
||||
// url-encoded, and server-side the params get decoded turning `+`
|
||||
// into spaces.
|
||||
EXPECT_EQ("explicitly encoded", res->body);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(BindServerTest, BindDualStack) {
|
||||
Server svr;
|
||||
|
||||
@@ -1344,7 +1378,10 @@ protected:
|
||||
(*i)++;
|
||||
return true;
|
||||
},
|
||||
[i] { delete i; });
|
||||
[i](bool success) {
|
||||
EXPECT_TRUE(success);
|
||||
delete i;
|
||||
});
|
||||
})
|
||||
.Get("/streamed",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -1366,11 +1403,15 @@ protected:
|
||||
const auto &d = *data;
|
||||
auto out_len =
|
||||
std::min(static_cast<size_t>(length), DATA_CHUNK_SIZE);
|
||||
auto ret = sink.write(&d[static_cast<size_t>(offset)], out_len);
|
||||
auto ret =
|
||||
sink.write(&d[static_cast<size_t>(offset)], out_len);
|
||||
EXPECT_TRUE(ret);
|
||||
return true;
|
||||
},
|
||||
[data] { delete data; });
|
||||
[data](bool success) {
|
||||
EXPECT_TRUE(success);
|
||||
delete data;
|
||||
});
|
||||
})
|
||||
.Get("/streamed-cancel",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -3145,12 +3186,11 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
|
||||
std::string *resp = nullptr) {
|
||||
auto error = Error::Success;
|
||||
|
||||
auto client_sock =
|
||||
detail::create_client_socket(HOST, PORT, AF_UNSPEC, false, nullptr,
|
||||
/*connection_timeout_sec=*/5, 0,
|
||||
/*read_timeout_sec=*/5, 0,
|
||||
/*write_timeout_sec=*/5, 0,
|
||||
std::string(), error);
|
||||
auto client_sock = detail::create_client_socket(
|
||||
HOST, PORT, AF_UNSPEC, false, nullptr,
|
||||
/*connection_timeout_sec=*/5, 0,
|
||||
/*read_timeout_sec=*/5, 0,
|
||||
/*write_timeout_sec=*/5, 0, std::string(), error);
|
||||
|
||||
if (client_sock == INVALID_SOCKET) { return false; }
|
||||
|
||||
@@ -3533,6 +3573,44 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ClientProblemDetectionTest, ContentProvider) {
|
||||
Server svr;
|
||||
|
||||
size_t content_length = 1024 * 1024;
|
||||
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content_provider(
|
||||
content_length, "text/plain",
|
||||
[&](size_t offset, size_t length, DataSink &sink) {
|
||||
auto out_len = std::min(length, static_cast<size_t>(1024));
|
||||
std::string out(out_len, '@');
|
||||
sink.write(out.data(), out_len);
|
||||
return offset < 4096;
|
||||
},
|
||||
[](bool success) { ASSERT_FALSE(success); });
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
auto res = cli.Get("/hi", [&](const char * /*data*/, size_t /*data_length*/) {
|
||||
return false;
|
||||
});
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
|
||||
Server svr;
|
||||
|
||||
@@ -3630,6 +3708,54 @@ TEST(GetWithParametersTest, GetWithParameters2) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ClientDefaultHeadersTest, DefaultHeaders) {
|
||||
Client cli("httpbin.org");
|
||||
cli.set_default_headers({make_range_header({{1, 10}})});
|
||||
cli.set_connection_timeout(5);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/range/32");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijk", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/range/32");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijk", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ServerDefaultHeadersTest, DefaultHeaders) {
|
||||
Server svr;
|
||||
svr.set_default_headers({{"Hello", "World"}});
|
||||
|
||||
svr.Get("/", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("ok", res->body);
|
||||
EXPECT_EQ("World", res->get_header_value("Hello"));
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, ReadTimeoutSSL) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
Reference in New Issue
Block a user