mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7992b14896 | |||
| 7e420aeed3 | |||
| 227d2c2050 | |||
| 93e53c91f7 | |||
| 58cffd3223 | |||
| 8f32271e8c | |||
| c8c1c3d376 | |||
| 9f512acb42 | |||
| c0b461a3b7 | |||
| 74fe5a5029 | |||
| 9d0a9d4e23 | |||
| 5758769ad3 | |||
| e7eadc3605 | |||
| 07c6e58951 | |||
| 87994811a1 | |||
| 42feb7e8be | |||
| 26196b70af | |||
| 93a51979c4 | |||
| ad7edc7b27 | |||
| 27cd4e6ffe | |||
| cae5a8be1c |
+3
-10
@@ -193,7 +193,7 @@ target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
cxx_std_11
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
@@ -221,15 +221,8 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
|
||||
)
|
||||
|
||||
# Cmake's find_package search path is different based on the system
|
||||
# See https://cmake.org/cmake/help/latest/command/find_package.html for the list
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_PREFIX}/cmake/${PROJECT_NAME}")
|
||||
else()
|
||||
# On Non-Windows, it should be /usr/lib/cmake/<name>/<name>Config.cmake
|
||||
# NOTE: This may or may not work for macOS...
|
||||
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
|
||||
endif()
|
||||
# CMake configuration files installation directory
|
||||
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ httplib::SSLServer svr("./cert.pem", "./key.pem");
|
||||
// Client
|
||||
httplib::Client cli("https://localhost:1234"); // scheme + host
|
||||
httplib::SSLClient cli("localhost:1234"); // host
|
||||
httplib::SSLClient cli("localhost", 1234); // host, port
|
||||
|
||||
// Use your CA bundle
|
||||
cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.11.2"
|
||||
#define CPPHTTPLIB_VERSION "0.11.4"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -172,7 +172,9 @@ using socket_t = SOCKET;
|
||||
#else // not _WIN32
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#ifndef _AIX
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
@@ -253,14 +255,10 @@ using socket_t = int;
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x1010100fL
|
||||
#error Sorry, OpenSSL versions prior to 1.1.1 are not supported
|
||||
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
|
||||
#define SSL_get1_peer_certificate SSL_get_peer_certificate
|
||||
#endif
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
#include <openssl/crypto.h>
|
||||
inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
|
||||
return M_ASN1_STRING_data(asn1);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
@@ -371,6 +369,14 @@ using ContentProviderWithoutLength =
|
||||
|
||||
using ContentProviderResourceReleaser = std::function<void(bool success)>;
|
||||
|
||||
struct MultipartFormDataProvider {
|
||||
std::string name;
|
||||
ContentProviderWithoutLength provider;
|
||||
std::string filename;
|
||||
std::string content_type;
|
||||
};
|
||||
using MultipartFormDataProviderItems = std::vector<MultipartFormDataProvider>;
|
||||
|
||||
using ContentReceiverWithProgress =
|
||||
std::function<bool(const char *data, size_t data_length, uint64_t offset,
|
||||
uint64_t total_length)>;
|
||||
@@ -415,6 +421,8 @@ struct Request {
|
||||
|
||||
std::string remote_addr;
|
||||
int remote_port = -1;
|
||||
std::string local_addr;
|
||||
int local_port = -1;
|
||||
|
||||
// for server
|
||||
std::string version;
|
||||
@@ -516,6 +524,7 @@ public:
|
||||
virtual ssize_t read(char *ptr, size_t size) = 0;
|
||||
virtual ssize_t write(const char *ptr, size_t size) = 0;
|
||||
virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
|
||||
virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
|
||||
virtual socket_t socket() const = 0;
|
||||
|
||||
template <typename... Args>
|
||||
@@ -548,8 +557,11 @@ public:
|
||||
~ThreadPool() override = default;
|
||||
|
||||
void enqueue(std::function<void()> fn) override {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
jobs_.push_back(std::move(fn));
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
jobs_.push_back(std::move(fn));
|
||||
}
|
||||
|
||||
cond_.notify_one();
|
||||
}
|
||||
|
||||
@@ -583,7 +595,7 @@ private:
|
||||
|
||||
if (pool_.shutdown_ && pool_.jobs_.empty()) { break; }
|
||||
|
||||
fn = pool_.jobs_.front();
|
||||
fn = std::move(pool_.jobs_.front());
|
||||
pool_.jobs_.pop_front();
|
||||
}
|
||||
|
||||
@@ -901,6 +913,7 @@ public:
|
||||
Result Head(const std::string &path, const Headers &headers);
|
||||
|
||||
Result Post(const std::string &path);
|
||||
Result Post(const std::string &path, const Headers &headers);
|
||||
Result Post(const std::string &path, const char *body, size_t content_length,
|
||||
const std::string &content_type);
|
||||
Result Post(const std::string &path, const Headers &headers, const char *body,
|
||||
@@ -929,6 +942,9 @@ public:
|
||||
const MultipartFormDataItems &items);
|
||||
Result Post(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items, const std::string &boundary);
|
||||
Result Post(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items,
|
||||
const MultipartFormDataProviderItems &provider_items);
|
||||
|
||||
Result Put(const std::string &path);
|
||||
Result Put(const std::string &path, const char *body, size_t content_length,
|
||||
@@ -958,6 +974,9 @@ public:
|
||||
const MultipartFormDataItems &items);
|
||||
Result Put(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items, const std::string &boundary);
|
||||
Result Put(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items,
|
||||
const MultipartFormDataProviderItems &provider_items);
|
||||
|
||||
Result Patch(const std::string &path);
|
||||
Result Patch(const std::string &path, const char *body, size_t content_length,
|
||||
@@ -1196,6 +1215,9 @@ private:
|
||||
ContentProvider content_provider,
|
||||
ContentProviderWithoutLength content_provider_without_length,
|
||||
const std::string &content_type);
|
||||
ContentProviderWithoutLength get_multipart_content_provider(
|
||||
const std::string &boundary, const MultipartFormDataItems &items,
|
||||
const MultipartFormDataProviderItems &provider_items);
|
||||
|
||||
std::string adjust_host_string(const std::string &host) const;
|
||||
|
||||
@@ -1262,6 +1284,7 @@ public:
|
||||
Result Head(const std::string &path, const Headers &headers);
|
||||
|
||||
Result Post(const std::string &path);
|
||||
Result Post(const std::string &path, const Headers &headers);
|
||||
Result Post(const std::string &path, const char *body, size_t content_length,
|
||||
const std::string &content_type);
|
||||
Result Post(const std::string &path, const Headers &headers, const char *body,
|
||||
@@ -1290,6 +1313,10 @@ public:
|
||||
const MultipartFormDataItems &items);
|
||||
Result Post(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items, const std::string &boundary);
|
||||
Result Post(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items,
|
||||
const MultipartFormDataProviderItems &provider_items);
|
||||
|
||||
Result Put(const std::string &path);
|
||||
Result Put(const std::string &path, const char *body, size_t content_length,
|
||||
const std::string &content_type);
|
||||
@@ -1318,6 +1345,10 @@ public:
|
||||
const MultipartFormDataItems &items);
|
||||
Result Put(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items, const std::string &boundary);
|
||||
Result Put(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items,
|
||||
const MultipartFormDataProviderItems &provider_items);
|
||||
|
||||
Result Patch(const std::string &path);
|
||||
Result Patch(const std::string &path, const char *body, size_t content_length,
|
||||
const std::string &content_type);
|
||||
@@ -1533,7 +1564,7 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) {
|
||||
auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
duration - std::chrono::seconds(sec))
|
||||
.count();
|
||||
callback(sec, usec);
|
||||
callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -1779,6 +1810,7 @@ public:
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
void get_local_ip_and_port(std::string &ip, int &port) const override;
|
||||
socket_t socket() const override;
|
||||
|
||||
const std::string &get_buffer() const;
|
||||
@@ -2447,6 +2479,7 @@ public:
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
void get_local_ip_and_port(std::string &ip, int &port) const override;
|
||||
socket_t socket() const override;
|
||||
|
||||
private:
|
||||
@@ -2476,6 +2509,7 @@ public:
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
void get_local_ip_and_port(std::string &ip, int &port) const override;
|
||||
socket_t socket() const override;
|
||||
|
||||
private:
|
||||
@@ -2600,6 +2634,9 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
hints.ai_addrlen = static_cast<socklen_t>(
|
||||
sizeof(addr) - sizeof(addr.sun_path) + addrlen);
|
||||
|
||||
fcntl(sock, F_SETFD, FD_CLOEXEC);
|
||||
if (socket_options) { socket_options(sock); }
|
||||
|
||||
if (!bind_or_connect(sock, hints)) {
|
||||
close_socket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
@@ -2720,7 +2757,7 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if !defined _WIN32 && !defined ANDROID
|
||||
#if !defined _WIN32 && !defined ANDROID && !defined _AIX
|
||||
#define USE_IF2IP
|
||||
#endif
|
||||
|
||||
@@ -2841,9 +2878,8 @@ inline socket_t create_client_socket(
|
||||
return sock;
|
||||
}
|
||||
|
||||
inline bool get_remote_ip_and_port(const struct sockaddr_storage &addr,
|
||||
socklen_t addr_len, std::string &ip,
|
||||
int &port) {
|
||||
inline bool get_ip_and_port(const struct sockaddr_storage &addr,
|
||||
socklen_t addr_len, std::string &ip, int &port) {
|
||||
if (addr.ss_family == AF_INET) {
|
||||
port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
@@ -2864,13 +2900,40 @@ inline bool get_remote_ip_and_port(const struct sockaddr_storage &addr,
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
if (!getsockname(sock, reinterpret_cast<struct sockaddr *>(&addr),
|
||||
&addr_len)) {
|
||||
get_ip_and_port(addr, addr_len, ip, port);
|
||||
}
|
||||
}
|
||||
|
||||
inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
|
||||
if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
|
||||
&addr_len)) {
|
||||
get_remote_ip_and_port(addr, addr_len, ip, port);
|
||||
#ifndef _WIN32
|
||||
if (addr.ss_family == AF_UNIX) {
|
||||
#if defined(__linux__)
|
||||
struct ucred ucred;
|
||||
socklen_t len = sizeof(ucred);
|
||||
if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) {
|
||||
port = ucred.pid;
|
||||
}
|
||||
#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__
|
||||
pid_t pid;
|
||||
socklen_t len = sizeof(pid);
|
||||
if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) {
|
||||
port = pid;
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
get_ip_and_port(addr, addr_len, ip, port);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4090,29 +4153,48 @@ inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
|
||||
return valid;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::string
|
||||
serialize_multipart_formdata_item_begin(const T &item,
|
||||
const std::string &boundary) {
|
||||
std::string body = "--" + boundary + "\r\n";
|
||||
body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
|
||||
if (!item.filename.empty()) {
|
||||
body += "; filename=\"" + item.filename + "\"";
|
||||
}
|
||||
body += "\r\n";
|
||||
if (!item.content_type.empty()) {
|
||||
body += "Content-Type: " + item.content_type + "\r\n";
|
||||
}
|
||||
body += "\r\n";
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
|
||||
|
||||
inline std::string
|
||||
serialize_multipart_formdata_finish(const std::string &boundary) {
|
||||
return "--" + boundary + "--\r\n";
|
||||
}
|
||||
|
||||
inline std::string
|
||||
serialize_multipart_formdata_get_content_type(const std::string &boundary) {
|
||||
return "multipart/form-data; boundary=" + boundary;
|
||||
}
|
||||
|
||||
inline std::string
|
||||
serialize_multipart_formdata(const MultipartFormDataItems &items,
|
||||
const std::string &boundary,
|
||||
std::string &content_type) {
|
||||
const std::string &boundary, bool finish = true) {
|
||||
std::string body;
|
||||
|
||||
for (const auto &item : items) {
|
||||
body += "--" + boundary + "\r\n";
|
||||
body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
|
||||
if (!item.filename.empty()) {
|
||||
body += "; filename=\"" + item.filename + "\"";
|
||||
}
|
||||
body += "\r\n";
|
||||
if (!item.content_type.empty()) {
|
||||
body += "Content-Type: " + item.content_type + "\r\n";
|
||||
}
|
||||
body += "\r\n";
|
||||
body += item.content + "\r\n";
|
||||
body += serialize_multipart_formdata_item_begin(item, boundary);
|
||||
body += item.content + serialize_multipart_formdata_item_end();
|
||||
}
|
||||
|
||||
body += "--" + boundary + "--\r\n";
|
||||
if (finish) body += serialize_multipart_formdata_finish(boundary);
|
||||
|
||||
content_type = "multipart/form-data; boundary=" + boundary;
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -4497,8 +4579,8 @@ inline void hosted_at(const std::string &hostname,
|
||||
*reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
|
||||
std::string ip;
|
||||
int dummy = -1;
|
||||
if (detail::get_remote_ip_and_port(addr, sizeof(struct sockaddr_storage),
|
||||
ip, dummy)) {
|
||||
if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
|
||||
dummy)) {
|
||||
addrs.push_back(ip);
|
||||
}
|
||||
}
|
||||
@@ -4723,7 +4805,7 @@ inline bool SocketStream::is_readable() const {
|
||||
|
||||
inline bool SocketStream::is_writable() const {
|
||||
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
|
||||
is_socket_alive(sock_);
|
||||
is_socket_alive(sock_);
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
@@ -4788,6 +4870,11 @@ inline void SocketStream::get_remote_ip_and_port(std::string &ip,
|
||||
return detail::get_remote_ip_and_port(sock_, ip, port);
|
||||
}
|
||||
|
||||
inline void SocketStream::get_local_ip_and_port(std::string &ip,
|
||||
int &port) const {
|
||||
return detail::get_local_ip_and_port(sock_, ip, port);
|
||||
}
|
||||
|
||||
inline socket_t SocketStream::socket() const { return sock_; }
|
||||
|
||||
// Buffer stream implementation
|
||||
@@ -4813,6 +4900,9 @@ inline ssize_t BufferStream::write(const char *ptr, size_t size) {
|
||||
inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
|
||||
int & /*port*/) const {}
|
||||
|
||||
inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
|
||||
int & /*port*/) const {}
|
||||
|
||||
inline socket_t BufferStream::socket() const { return 0; }
|
||||
|
||||
inline const std::string &BufferStream::get_buffer() const { return buffer; }
|
||||
@@ -5460,6 +5550,8 @@ inline bool Server::listen_internal() {
|
||||
// Try to accept new connections after a short sleep.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
} else if (errno == EINTR || errno == EAGAIN) {
|
||||
continue;
|
||||
}
|
||||
if (svr_sock_ != INVALID_SOCKET) {
|
||||
detail::close_socket(svr_sock_);
|
||||
@@ -5792,6 +5884,10 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
req.set_header("REMOTE_ADDR", req.remote_addr);
|
||||
req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
|
||||
|
||||
strm.get_local_ip_and_port(req.local_addr, req.local_port);
|
||||
req.set_header("LOCAL_ADDR", req.local_addr);
|
||||
req.set_header("LOCAL_PORT", std::to_string(req.local_port));
|
||||
|
||||
if (req.has_header("Range")) {
|
||||
const auto &range_header_value = req.get_header_value("Range");
|
||||
if (!detail::parse_range_header(range_header_value, req.ranges)) {
|
||||
@@ -5831,7 +5927,16 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
routed = true;
|
||||
} else {
|
||||
res.status = 500;
|
||||
res.set_header("EXCEPTION_WHAT", e.what());
|
||||
std::string val;
|
||||
auto s = e.what();
|
||||
for (size_t i = 0; s[i]; i++) {
|
||||
switch (s[i]) {
|
||||
case '\r': val += "\\r"; break;
|
||||
case '\n': val += "\\n"; break;
|
||||
default: val += s[i]; break;
|
||||
}
|
||||
}
|
||||
res.set_header("EXCEPTION_WHAT", val);
|
||||
}
|
||||
} catch (...) {
|
||||
if (exception_handler_) {
|
||||
@@ -6585,6 +6690,49 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
return true;
|
||||
}
|
||||
|
||||
inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
|
||||
const std::string &boundary, const MultipartFormDataItems &items,
|
||||
const MultipartFormDataProviderItems &provider_items) {
|
||||
size_t cur_item = 0, cur_start = 0;
|
||||
// cur_item and cur_start are copied to within the std::function and maintain
|
||||
// state between successive calls
|
||||
return [&, cur_item, cur_start](size_t offset,
|
||||
DataSink &sink) mutable -> bool {
|
||||
if (!offset && items.size()) {
|
||||
sink.os << detail::serialize_multipart_formdata(items, boundary, false);
|
||||
return true;
|
||||
} else if (cur_item < provider_items.size()) {
|
||||
if (!cur_start) {
|
||||
const auto &begin = detail::serialize_multipart_formdata_item_begin(
|
||||
provider_items[cur_item], boundary);
|
||||
offset += begin.size();
|
||||
cur_start = offset;
|
||||
sink.os << begin;
|
||||
}
|
||||
|
||||
DataSink cur_sink;
|
||||
bool has_data = true;
|
||||
cur_sink.write = sink.write;
|
||||
cur_sink.done = [&]() { has_data = false; };
|
||||
cur_sink.is_writable = sink.is_writable;
|
||||
|
||||
if (!provider_items[cur_item].provider(offset - cur_start, cur_sink))
|
||||
return false;
|
||||
|
||||
if (!has_data) {
|
||||
sink.os << detail::serialize_multipart_formdata_item_end();
|
||||
cur_item++;
|
||||
cur_start = 0;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
sink.os << detail::serialize_multipart_formdata_finish(boundary);
|
||||
sink.done();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
inline bool
|
||||
ClientImpl::process_socket(const Socket &socket,
|
||||
std::function<bool(Stream &strm)> callback) {
|
||||
@@ -6730,6 +6878,11 @@ inline Result ClientImpl::Post(const std::string &path) {
|
||||
return Post(path, std::string(), std::string());
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Post(const std::string &path,
|
||||
const Headers &headers) {
|
||||
return Post(path, headers, nullptr, 0, std::string());
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Post(const std::string &path, const char *body,
|
||||
size_t content_length,
|
||||
const std::string &content_type) {
|
||||
@@ -6802,9 +6955,10 @@ inline Result ClientImpl::Post(const std::string &path,
|
||||
|
||||
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items) {
|
||||
std::string content_type;
|
||||
const auto &body = detail::serialize_multipart_formdata(
|
||||
items, detail::make_multipart_data_boundary(), content_type);
|
||||
const auto &boundary = detail::make_multipart_data_boundary();
|
||||
const auto &content_type =
|
||||
detail::serialize_multipart_formdata_get_content_type(boundary);
|
||||
const auto &body = detail::serialize_multipart_formdata(items, boundary);
|
||||
return Post(path, headers, body, content_type.c_str());
|
||||
}
|
||||
|
||||
@@ -6815,12 +6969,25 @@ inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
|
||||
return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
|
||||
}
|
||||
|
||||
std::string content_type;
|
||||
const auto &body =
|
||||
detail::serialize_multipart_formdata(items, boundary, content_type);
|
||||
const auto &content_type =
|
||||
detail::serialize_multipart_formdata_get_content_type(boundary);
|
||||
const auto &body = detail::serialize_multipart_formdata(items, boundary);
|
||||
return Post(path, headers, body, content_type.c_str());
|
||||
}
|
||||
|
||||
inline Result
|
||||
ClientImpl::Post(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items,
|
||||
const MultipartFormDataProviderItems &provider_items) {
|
||||
const auto &boundary = detail::make_multipart_data_boundary();
|
||||
const auto &content_type =
|
||||
detail::serialize_multipart_formdata_get_content_type(boundary);
|
||||
return send_with_content_provider(
|
||||
"POST", path, headers, nullptr, 0, nullptr,
|
||||
get_multipart_content_provider(boundary, items, provider_items),
|
||||
content_type);
|
||||
}
|
||||
|
||||
inline Result ClientImpl::Put(const std::string &path) {
|
||||
return Put(path, std::string(), std::string());
|
||||
}
|
||||
@@ -6897,9 +7064,10 @@ inline Result ClientImpl::Put(const std::string &path,
|
||||
|
||||
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items) {
|
||||
std::string content_type;
|
||||
const auto &body = detail::serialize_multipart_formdata(
|
||||
items, detail::make_multipart_data_boundary(), content_type);
|
||||
const auto &boundary = detail::make_multipart_data_boundary();
|
||||
const auto &content_type =
|
||||
detail::serialize_multipart_formdata_get_content_type(boundary);
|
||||
const auto &body = detail::serialize_multipart_formdata(items, boundary);
|
||||
return Put(path, headers, body, content_type);
|
||||
}
|
||||
|
||||
@@ -6910,12 +7078,24 @@ inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
|
||||
return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
|
||||
}
|
||||
|
||||
std::string content_type;
|
||||
const auto &body =
|
||||
detail::serialize_multipart_formdata(items, boundary, content_type);
|
||||
const auto &content_type =
|
||||
detail::serialize_multipart_formdata_get_content_type(boundary);
|
||||
const auto &body = detail::serialize_multipart_formdata(items, boundary);
|
||||
return Put(path, headers, body, content_type);
|
||||
}
|
||||
|
||||
inline Result
|
||||
ClientImpl::Put(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items,
|
||||
const MultipartFormDataProviderItems &provider_items) {
|
||||
const auto &boundary = detail::make_multipart_data_boundary();
|
||||
const auto &content_type =
|
||||
detail::serialize_multipart_formdata_get_content_type(boundary);
|
||||
return send_with_content_provider(
|
||||
"PUT", path, headers, nullptr, 0, nullptr,
|
||||
get_multipart_content_provider(boundary, items, provider_items),
|
||||
content_type);
|
||||
}
|
||||
inline Result ClientImpl::Patch(const std::string &path) {
|
||||
return Patch(path, std::string(), std::string());
|
||||
}
|
||||
@@ -7275,55 +7455,12 @@ process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
|
||||
return callback(strm);
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
static std::shared_ptr<std::vector<std::mutex>> openSSL_locks_;
|
||||
|
||||
class SSLThreadLocks {
|
||||
public:
|
||||
SSLThreadLocks() {
|
||||
openSSL_locks_ =
|
||||
std::make_shared<std::vector<std::mutex>>(CRYPTO_num_locks());
|
||||
CRYPTO_set_locking_callback(locking_callback);
|
||||
}
|
||||
|
||||
~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); }
|
||||
|
||||
private:
|
||||
static void locking_callback(int mode, int type, const char * /*file*/,
|
||||
int /*line*/) {
|
||||
auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
|
||||
if (mode & CRYPTO_LOCK) {
|
||||
lk.lock();
|
||||
} else {
|
||||
lk.unlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
class SSLInit {
|
||||
public:
|
||||
SSLInit() {
|
||||
#if OPENSSL_VERSION_NUMBER < 0x1010001fL
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
#else
|
||||
OPENSSL_init_ssl(
|
||||
OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
~SSLInit() {
|
||||
#if OPENSSL_VERSION_NUMBER < 0x1010001fL
|
||||
ERR_free_strings();
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
SSLThreadLocks thread_init_;
|
||||
#endif
|
||||
};
|
||||
|
||||
// SSL socket stream implementation
|
||||
@@ -7347,7 +7484,7 @@ inline bool SSLSocketStream::is_readable() const {
|
||||
|
||||
inline bool SSLSocketStream::is_writable() const {
|
||||
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
|
||||
is_socket_alive(sock_);
|
||||
is_socket_alive(sock_);
|
||||
}
|
||||
|
||||
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
@@ -7418,6 +7555,11 @@ inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
|
||||
detail::get_remote_ip_and_port(sock_, ip, port);
|
||||
}
|
||||
|
||||
inline void SSLSocketStream::get_local_ip_and_port(std::string &ip,
|
||||
int &port) const {
|
||||
detail::get_local_ip_and_port(sock_, ip, port);
|
||||
}
|
||||
|
||||
inline socket_t SSLSocketStream::socket() const { return sock_; }
|
||||
|
||||
static SSLInit sslinit_;
|
||||
@@ -7727,7 +7869,7 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto server_cert = SSL_get_peer_certificate(ssl2);
|
||||
auto server_cert = SSL_get1_peer_certificate(ssl2);
|
||||
|
||||
if (server_cert == nullptr) {
|
||||
error = Error::SSLServerVerification;
|
||||
@@ -8053,6 +8195,9 @@ inline Result Client::Head(const std::string &path, const Headers &headers) {
|
||||
}
|
||||
|
||||
inline Result Client::Post(const std::string &path) { return cli_->Post(path); }
|
||||
inline Result Client::Post(const std::string &path, const Headers &headers) {
|
||||
return cli_->Post(path, headers);
|
||||
}
|
||||
inline Result Client::Post(const std::string &path, const char *body,
|
||||
size_t content_length,
|
||||
const std::string &content_type) {
|
||||
@@ -8115,6 +8260,12 @@ inline Result Client::Post(const std::string &path, const Headers &headers,
|
||||
const std::string &boundary) {
|
||||
return cli_->Post(path, headers, items, boundary);
|
||||
}
|
||||
inline Result
|
||||
Client::Post(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items,
|
||||
const MultipartFormDataProviderItems &provider_items) {
|
||||
return cli_->Post(path, headers, items, provider_items);
|
||||
}
|
||||
inline Result Client::Put(const std::string &path) { return cli_->Put(path); }
|
||||
inline Result Client::Put(const std::string &path, const char *body,
|
||||
size_t content_length,
|
||||
@@ -8178,6 +8329,12 @@ inline Result Client::Put(const std::string &path, const Headers &headers,
|
||||
const std::string &boundary) {
|
||||
return cli_->Put(path, headers, items, boundary);
|
||||
}
|
||||
inline Result
|
||||
Client::Put(const std::string &path, const Headers &headers,
|
||||
const MultipartFormDataItems &items,
|
||||
const MultipartFormDataProviderItems &provider_items) {
|
||||
return cli_->Put(path, headers, items, provider_items);
|
||||
}
|
||||
inline Result Client::Patch(const std::string &path) {
|
||||
return cli_->Patch(path);
|
||||
}
|
||||
@@ -8383,4 +8540,8 @@ inline SSL_CTX *Client::ssl_context() const {
|
||||
|
||||
} // namespace httplib
|
||||
|
||||
#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL)
|
||||
#undef poll
|
||||
#endif
|
||||
|
||||
#endif // CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -22,8 +22,6 @@ public:
|
||||
|
||||
ssize_t write(const std::string &s) { return write(s.data(), s.size()); }
|
||||
|
||||
std::string get_remote_addr() const { return ""; }
|
||||
|
||||
bool is_readable() const override { return true; }
|
||||
|
||||
bool is_writable() const override { return true; }
|
||||
@@ -33,6 +31,11 @@ public:
|
||||
port = 8080;
|
||||
}
|
||||
|
||||
void get_local_ip_and_port(std::string &ip, int &port) const override {
|
||||
ip = "127.0.0.1";
|
||||
port = 8080;
|
||||
}
|
||||
|
||||
socket_t socket() const override { return 0; }
|
||||
|
||||
private:
|
||||
|
||||
+443
-78
@@ -1,10 +1,12 @@
|
||||
#include <httplib.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
@@ -942,6 +944,44 @@ TEST(UrlWithSpace, Redirect_Online) {
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
TEST(ReceiveSignals, Signal) {
|
||||
auto setupSignalHandlers = []() {
|
||||
struct sigaction act;
|
||||
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_SIGINFO;
|
||||
act.sa_sigaction = [](int sig, siginfo_t *, void *) {
|
||||
switch (sig) {
|
||||
case SIGINT:
|
||||
default: break;
|
||||
}
|
||||
};
|
||||
::sigaction(SIGINT, &act, nullptr);
|
||||
};
|
||||
|
||||
Server svr;
|
||||
int port = 0;
|
||||
auto thread = std::thread([&]() {
|
||||
setupSignalHandlers();
|
||||
port = svr.bind_to_any_port("localhost");
|
||||
svr.listen_after_bind();
|
||||
});
|
||||
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
pthread_kill(thread.native_handle(), SIGINT);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(RedirectToDifferentPort, Redirect) {
|
||||
Server svr1;
|
||||
svr1.Get("/1", [&](const Request & /*req*/, Response &res) {
|
||||
@@ -1324,7 +1364,12 @@ TEST(NoContentTest, ContentLength) {
|
||||
}
|
||||
|
||||
TEST(RoutingHandlerTest, PreRoutingHandler) {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
#else
|
||||
Server svr;
|
||||
#endif
|
||||
|
||||
svr.set_pre_routing_handler([](const Request &req, Response &res) {
|
||||
if (req.path == "/routing_handler") {
|
||||
@@ -1355,7 +1400,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(HOST, PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
#else
|
||||
Client cli(HOST, PORT);
|
||||
#endif
|
||||
|
||||
auto res = cli.Get("/routing_handler");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -1368,7 +1418,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
|
||||
}
|
||||
|
||||
{
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(HOST, PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
#else
|
||||
Client cli(HOST, PORT);
|
||||
#endif
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -1379,7 +1434,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
|
||||
}
|
||||
|
||||
{
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(HOST, PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
#else
|
||||
Client cli(HOST, PORT);
|
||||
#endif
|
||||
|
||||
auto res = cli.Get("/aaa");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -1521,6 +1581,17 @@ protected:
|
||||
std::stoi(req.get_header_value("REMOTE_PORT")));
|
||||
res.set_content(remote_addr.c_str(), "text/plain");
|
||||
})
|
||||
.Get("/local_addr",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_TRUE(req.has_header("LOCAL_PORT"));
|
||||
EXPECT_TRUE(req.has_header("LOCAL_ADDR"));
|
||||
auto local_addr = req.get_header_value("LOCAL_ADDR");
|
||||
auto local_port = req.get_header_value("LOCAL_PORT");
|
||||
EXPECT_EQ(req.local_addr, local_addr);
|
||||
EXPECT_EQ(req.local_port, std::stoi(local_port));
|
||||
res.set_content(local_addr.append(":").append(local_port),
|
||||
"text/plain");
|
||||
})
|
||||
.Get("/endwith%",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
@@ -1715,6 +1786,22 @@ protected:
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
res.set_content("empty-no-content-type", "text/plain");
|
||||
})
|
||||
.Post("/path-only",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
EXPECT_EQ("", req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
res.set_content("path-only", "text/plain");
|
||||
})
|
||||
.Post("/path-headers-only",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
EXPECT_EQ("", req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
EXPECT_EQ("world", req.get_header_value("hello"));
|
||||
EXPECT_EQ("world2", req.get_header_value("hello2"));
|
||||
res.set_content("path-headers-only", "text/plain");
|
||||
})
|
||||
.Post("/post-large",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, LARGE_DATA);
|
||||
@@ -2125,6 +2212,21 @@ TEST_F(ServerTest, PostEmptyContentWithNoContentType) {
|
||||
ASSERT_EQ("empty-no-content-type", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostPathOnly) {
|
||||
auto res = cli_.Post("/path-only");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("path-only", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostPathAndHeadersOnly) {
|
||||
auto res = cli_.Post("/path-headers-only",
|
||||
Headers({{"hello", "world"}, {"hello2", "world2"}}));
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("path-headers-only", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostLarge) {
|
||||
auto res = cli_.Post("/post-large", LARGE_DATA, "text/plain");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -2779,6 +2881,15 @@ TEST_F(ServerTest, GetMethodRemoteAddr) {
|
||||
EXPECT_TRUE(res->body == "::1" || res->body == "127.0.0.1");
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethodLocalAddr) {
|
||||
auto res = cli_.Get("/local_addr");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_TRUE(res->body == std::string("::1:").append(to_string(PORT)) ||
|
||||
res->body == std::string("127.0.0.1:").append(to_string(PORT)));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HTTPResponseSplitting) {
|
||||
auto res = cli_.Get("/http_response_splitting");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -3808,9 +3919,12 @@ TEST(MountTest, Unmount) {
|
||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
svr.Get("/exception", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception...");
|
||||
// res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/unknown", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception\r\n...");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
@@ -3823,11 +3937,21 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
{
|
||||
auto res = cli.Get("/exception");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/unknown");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception\\r\\n...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
@@ -4181,7 +4305,8 @@ protected:
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(std::string(), PORT, AI_PASSIVE)); });
|
||||
t_ = thread(
|
||||
[&]() { ASSERT_TRUE(svr_.listen(std::string(), PORT, AI_PASSIVE)); });
|
||||
|
||||
while (!svr_.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
@@ -4753,7 +4878,7 @@ TEST(SendAPI, SimpleInterface_Online) {
|
||||
|
||||
TEST(ClientImplMethods, GetSocketTest) {
|
||||
httplib::Server svr;
|
||||
svr.Get( "/", [&](const httplib::Request& /*req*/, httplib::Response& res) {
|
||||
svr.Get("/", [&](const httplib::Request & /*req*/, httplib::Response &res) {
|
||||
res.status = 200;
|
||||
});
|
||||
|
||||
@@ -5021,6 +5146,204 @@ TEST(MultipartFormDataTest, LargeData) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, DataProviderItems) {
|
||||
|
||||
std::random_device seed_gen;
|
||||
std::mt19937 random(seed_gen());
|
||||
|
||||
std::string rand1;
|
||||
rand1.resize(1000);
|
||||
std::generate(rand1.begin(), rand1.end(), [&]() { return random(); });
|
||||
|
||||
std::string rand2;
|
||||
rand2.resize(3000);
|
||||
std::generate(rand2.begin(), rand2.end(), [&]() { return random(); });
|
||||
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
svr.Post("/post-none", [&](const Request &req, Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
ASSERT_FALSE(req.is_multipart_form_data());
|
||||
|
||||
std::string body;
|
||||
content_reader([&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
EXPECT_EQ(body, "");
|
||||
});
|
||||
|
||||
svr.Post("/post-items", [&](const Request &req, Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
ASSERT_TRUE(req.is_multipart_form_data());
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(files.size() == 2);
|
||||
|
||||
EXPECT_EQ(std::string(files[0].name), "name1");
|
||||
EXPECT_EQ(files[0].content, "Testing123");
|
||||
EXPECT_EQ(files[0].filename, "filename1");
|
||||
EXPECT_EQ(files[0].content_type, "application/octet-stream");
|
||||
|
||||
EXPECT_EQ(files[1].name, "name2");
|
||||
EXPECT_EQ(files[1].content, "Testing456");
|
||||
EXPECT_EQ(files[1].filename, "");
|
||||
EXPECT_EQ(files[1].content_type, "");
|
||||
});
|
||||
|
||||
svr.Post("/post-providers", [&](const Request &req, Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
ASSERT_TRUE(req.is_multipart_form_data());
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(files.size() == 2);
|
||||
|
||||
EXPECT_EQ(files[0].name, "name3");
|
||||
EXPECT_EQ(files[0].content, rand1);
|
||||
EXPECT_EQ(files[0].filename, "filename3");
|
||||
EXPECT_EQ(files[0].content_type, "");
|
||||
|
||||
EXPECT_EQ(files[1].name, "name4");
|
||||
EXPECT_EQ(files[1].content, rand2);
|
||||
EXPECT_EQ(files[1].filename, "filename4");
|
||||
EXPECT_EQ(files[1].content_type, "");
|
||||
});
|
||||
|
||||
svr.Post("/post-both", [&](const Request &req, Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
ASSERT_TRUE(req.is_multipart_form_data());
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(files.size() == 4);
|
||||
|
||||
EXPECT_EQ(std::string(files[0].name), "name1");
|
||||
EXPECT_EQ(files[0].content, "Testing123");
|
||||
EXPECT_EQ(files[0].filename, "filename1");
|
||||
EXPECT_EQ(files[0].content_type, "application/octet-stream");
|
||||
|
||||
EXPECT_EQ(files[1].name, "name2");
|
||||
EXPECT_EQ(files[1].content, "Testing456");
|
||||
EXPECT_EQ(files[1].filename, "");
|
||||
EXPECT_EQ(files[1].content_type, "");
|
||||
|
||||
EXPECT_EQ(files[2].name, "name3");
|
||||
EXPECT_EQ(files[2].content, rand1);
|
||||
EXPECT_EQ(files[2].filename, "filename3");
|
||||
EXPECT_EQ(files[2].content_type, "");
|
||||
|
||||
EXPECT_EQ(files[3].name, "name4");
|
||||
EXPECT_EQ(files[3].content, rand2);
|
||||
EXPECT_EQ(files[3].filename, "filename4");
|
||||
EXPECT_EQ(files[3].content_type, "");
|
||||
});
|
||||
|
||||
auto t = std::thread([&]() { svr.listen("localhost", 8080); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli("https://localhost:8080");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
MultipartFormDataItems items{
|
||||
{"name1", "Testing123", "filename1", "application/octet-stream"},
|
||||
{"name2", "Testing456", "", ""}, // not a file
|
||||
};
|
||||
|
||||
{
|
||||
auto res = cli.Post("/post-none", {}, {}, {});
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
MultipartFormDataProviderItems providers;
|
||||
|
||||
{
|
||||
auto res =
|
||||
cli.Post("/post-items", {}, items, providers); // empty providers
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
providers.push_back({"name3",
|
||||
[&](size_t offset, httplib::DataSink &sink) -> bool {
|
||||
// test the offset is given correctly at each step
|
||||
if (!offset)
|
||||
sink.os.write(rand1.data(), 30);
|
||||
else if (offset == 30)
|
||||
sink.os.write(rand1.data() + 30, 300);
|
||||
else if (offset == 330)
|
||||
sink.os.write(rand1.data() + 330, 670);
|
||||
else if (offset == rand1.size())
|
||||
sink.done();
|
||||
return true;
|
||||
},
|
||||
"filename3",
|
||||
{}});
|
||||
|
||||
providers.push_back({"name4",
|
||||
[&](size_t offset, httplib::DataSink &sink) -> bool {
|
||||
// test the offset is given correctly at each step
|
||||
if (!offset)
|
||||
sink.os.write(rand2.data(), 2000);
|
||||
else if (offset == 2000)
|
||||
sink.os.write(rand2.data() + 2000, 1);
|
||||
else if (offset == 2001)
|
||||
sink.os.write(rand2.data() + 2001, 999);
|
||||
else if (offset == rand2.size())
|
||||
sink.done();
|
||||
return true;
|
||||
},
|
||||
"filename4",
|
||||
{}});
|
||||
|
||||
{
|
||||
auto res = cli.Post("/post-providers", {}, {}, providers);
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Post("/post-both", {}, items, providers);
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, WithPreamble) {
|
||||
Server svr;
|
||||
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
||||
@@ -5067,7 +5390,7 @@ TEST(MultipartFormDataTest, PostCustomBoundary) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
svr.Post("/post_customboundary", [&](const Request &req, Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
const ContentReader &content_reader) {
|
||||
if (req.is_multipart_form_data()) {
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
@@ -5127,7 +5450,7 @@ TEST(MultipartFormDataTest, PostCustomBoundary) {
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, PostInvalidBoundaryChars) {
|
||||
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
std::string data(1024 * 1024 * 2, '&');
|
||||
@@ -5141,12 +5464,12 @@ TEST(MultipartFormDataTest, PostInvalidBoundaryChars) {
|
||||
{"hello", "world", "", ""},
|
||||
};
|
||||
|
||||
for (const char& c: " \t\r\n") {
|
||||
auto res = cli.Post("/invalid_boundary", {}, items, string("abc123").append(1, c));
|
||||
for (const char &c : " \t\r\n") {
|
||||
auto res =
|
||||
cli.Post("/invalid_boundary", {}, items, string("abc123").append(1, c));
|
||||
ASSERT_EQ(Error::UnsupportedMultipartBoundaryChars, res.error());
|
||||
ASSERT_FALSE(res);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, PutFormData) {
|
||||
@@ -5215,38 +5538,39 @@ TEST(MultipartFormDataTest, PutFormData) {
|
||||
TEST(MultipartFormDataTest, PutFormDataCustomBoundary) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
svr.Put("/put_customboundary", [&](const Request &req, const Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
if (req.is_multipart_form_data()) {
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
svr.Put("/put_customboundary",
|
||||
[&](const Request &req, const Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
if (req.is_multipart_form_data()) {
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
EXPECT_TRUE(std::string(files[0].name) == "document");
|
||||
EXPECT_EQ(size_t(1024 * 1024 * 2), files[0].content.size());
|
||||
EXPECT_TRUE(files[0].filename == "2MB_data");
|
||||
EXPECT_TRUE(files[0].content_type == "application/octet-stream");
|
||||
|
||||
EXPECT_TRUE(files[1].name == "hello");
|
||||
EXPECT_TRUE(files[1].content == "world");
|
||||
EXPECT_TRUE(files[1].filename == "");
|
||||
EXPECT_TRUE(files[1].content_type == "");
|
||||
} else {
|
||||
std::string body;
|
||||
content_reader([&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_TRUE(std::string(files[0].name) == "document");
|
||||
EXPECT_EQ(size_t(1024 * 1024 * 2), files[0].content.size());
|
||||
EXPECT_TRUE(files[0].filename == "2MB_data");
|
||||
EXPECT_TRUE(files[0].content_type == "application/octet-stream");
|
||||
|
||||
EXPECT_TRUE(files[1].name == "hello");
|
||||
EXPECT_TRUE(files[1].content == "world");
|
||||
EXPECT_TRUE(files[1].filename == "");
|
||||
EXPECT_TRUE(files[1].content_type == "");
|
||||
} else {
|
||||
std::string body;
|
||||
content_reader([&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
auto t = std::thread([&]() { svr.listen("localhost", 8080); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
@@ -5291,7 +5615,7 @@ TEST(MultipartFormDataTest, PutInvalidBoundaryChars) {
|
||||
{"hello", "world", "", ""},
|
||||
};
|
||||
|
||||
for (const char& c: " \t\r\n") {
|
||||
for (const char &c : " \t\r\n") {
|
||||
auto res = cli.Put("/put", {}, items, string("abc123").append(1, c));
|
||||
ASSERT_EQ(Error::UnsupportedMultipartBoundaryChars, res.error());
|
||||
ASSERT_FALSE(res);
|
||||
@@ -5303,9 +5627,7 @@ TEST(MultipartFormDataTest, PutInvalidBoundaryChars) {
|
||||
#ifndef _WIN32
|
||||
class UnixSocketTest : public ::testing::Test {
|
||||
protected:
|
||||
void TearDown() override {
|
||||
std::remove(pathname_.c_str());
|
||||
}
|
||||
void TearDown() override { std::remove(pathname_.c_str()); }
|
||||
|
||||
void client_GET(const std::string &addr) {
|
||||
httplib::Client cli{addr};
|
||||
@@ -5320,9 +5642,9 @@ protected:
|
||||
EXPECT_EQ(resp.body, content_);
|
||||
}
|
||||
|
||||
const std::string pathname_ {"./httplib-server.sock"};
|
||||
const std::string pattern_ {"/hi"};
|
||||
const std::string content_ {"Hello World!"};
|
||||
const std::string pathname_{"./httplib-server.sock"};
|
||||
const std::string pattern_{"/hi"};
|
||||
const std::string content_{"Hello World!"};
|
||||
};
|
||||
|
||||
TEST_F(UnixSocketTest, pathname) {
|
||||
@@ -5331,8 +5653,9 @@ TEST_F(UnixSocketTest, pathname) {
|
||||
res.set_content(content_, "text/plain");
|
||||
});
|
||||
|
||||
std::thread t {[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80)); }};
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
|
||||
}};
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
@@ -5344,18 +5667,45 @@ TEST_F(UnixSocketTest, pathname) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
#if defined(__linux__) || \
|
||||
/* __APPLE__ */ (defined(SOL_LOCAL) && defined(SO_PEERPID))
|
||||
TEST_F(UnixSocketTest, PeerPid) {
|
||||
httplib::Server svr;
|
||||
std::string remote_port_val;
|
||||
svr.Get(pattern_, [&](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
remote_port_val = req.get_header_value("REMOTE_PORT");
|
||||
});
|
||||
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
|
||||
}};
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
EXPECT_EQ(std::to_string(getpid()), remote_port_val);
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
TEST_F(UnixSocketTest, abstract) {
|
||||
constexpr char svr_path[] {"\x00httplib-server.sock"};
|
||||
const std::string abstract_addr {svr_path, sizeof(svr_path) - 1};
|
||||
constexpr char svr_path[]{"\x00httplib-server.sock"};
|
||||
const std::string abstract_addr{svr_path, sizeof(svr_path) - 1};
|
||||
|
||||
httplib::Server svr;
|
||||
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
});
|
||||
|
||||
std::thread t {[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(abstract_addr, 80)); }};
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(abstract_addr, 80));
|
||||
}};
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
@@ -5369,58 +5719,58 @@ TEST_F(UnixSocketTest, abstract) {
|
||||
#endif
|
||||
|
||||
TEST(SocketStream, is_writable_UNIX) {
|
||||
int fd[2];
|
||||
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fd));
|
||||
int fds[2];
|
||||
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
|
||||
|
||||
const auto asSocketStream =
|
||||
[&] (socket_t fd, std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
};
|
||||
asSocketStream(fd[0], [&] (Stream &s0) {
|
||||
EXPECT_EQ(s0.socket(), fd[0]);
|
||||
const auto asSocketStream = [&](socket_t fd,
|
||||
std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
};
|
||||
asSocketStream(fds[0], [&](Stream &s0) {
|
||||
EXPECT_EQ(s0.socket(), fds[0]);
|
||||
EXPECT_TRUE(s0.is_writable());
|
||||
|
||||
EXPECT_EQ(0, close(fd[1]));
|
||||
EXPECT_EQ(0, close(fds[1]));
|
||||
EXPECT_FALSE(s0.is_writable());
|
||||
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ(0, close(fd[0]));
|
||||
EXPECT_EQ(0, close(fds[0]));
|
||||
}
|
||||
|
||||
TEST(SocketStream, is_writable_INET) {
|
||||
sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(PORT+1);
|
||||
addr.sin_port = htons(PORT + 1);
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
|
||||
int disconnected_svr_sock = -1;
|
||||
std::thread svr {[&] {
|
||||
std::thread svr{[&] {
|
||||
const int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, s);
|
||||
ASSERT_EQ(0, ::bind(s, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)));
|
||||
ASSERT_EQ(0, ::bind(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
|
||||
ASSERT_EQ(0, listen(s, 1));
|
||||
ASSERT_LE(0, disconnected_svr_sock = accept(s, nullptr, nullptr));
|
||||
ASSERT_EQ(0, close(s));
|
||||
}};
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
std::thread cli {[&] {
|
||||
std::thread cli{[&] {
|
||||
const int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, s);
|
||||
ASSERT_EQ(0, connect(s, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)));
|
||||
ASSERT_EQ(0, connect(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
|
||||
ASSERT_EQ(0, close(s));
|
||||
}};
|
||||
cli.join();
|
||||
svr.join();
|
||||
ASSERT_NE(disconnected_svr_sock, -1);
|
||||
|
||||
const auto asSocketStream =
|
||||
[&] (socket_t fd, std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
};
|
||||
asSocketStream(disconnected_svr_sock, [&] (Stream &ss) {
|
||||
const auto asSocketStream = [&](socket_t fd,
|
||||
std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
};
|
||||
asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
|
||||
EXPECT_EQ(ss.socket(), disconnected_svr_sock);
|
||||
EXPECT_FALSE(ss.is_writable());
|
||||
|
||||
@@ -5429,4 +5779,19 @@ TEST(SocketStream, is_writable_INET) {
|
||||
|
||||
ASSERT_EQ(0, close(disconnected_svr_sock));
|
||||
}
|
||||
#endif // #ifndef _WIN32
|
||||
#endif // #ifndef _WIN32
|
||||
|
||||
TEST(TaskQueueTest, IncreaseAtomicInteger) {
|
||||
static constexpr unsigned int number_of_task{1000000};
|
||||
std::atomic_uint count{0};
|
||||
std::unique_ptr<TaskQueue> task_queue{
|
||||
new ThreadPool{CPPHTTPLIB_THREAD_POOL_COUNT}};
|
||||
|
||||
for (unsigned int i = 0; i < number_of_task; ++i) {
|
||||
task_queue->enqueue(
|
||||
[&count] { count.fetch_add(1, std::memory_order_relaxed); });
|
||||
}
|
||||
|
||||
EXPECT_NO_THROW(task_queue->shutdown());
|
||||
EXPECT_EQ(number_of_task, count.load());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user