mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d87d0672a8 | |||
| 3da42fd1e8 | |||
| 503aa61325 | |||
| e4c276d0c2 | |||
| e07f7691a8 | |||
| 623ab4a96e | |||
| e1efa337a2 | |||
| 549cdf2f7d | |||
| 3c522386e9 | |||
| c202aa9ce9 | |||
| e3e28c6231 | |||
| 4e05368086 | |||
| e1afe74fe2 | |||
| 461acb02f5 | |||
| 415edc237c | |||
| e20ecd2574 |
@@ -39,3 +39,55 @@ jobs:
|
||||
cd test
|
||||
msbuild.exe test.sln /verbosity:minimal /t:Build "/p:Configuration=Release;Platform=x64"
|
||||
x64\Release\test.exe
|
||||
|
||||
|
||||
meson-build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- name: Prepare Git for checkout on Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install dependencies on Linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get -qq update && sudo apt-get -qq install meson libssl-dev zlib1g-dev libbrotli-dev libgtest-dev
|
||||
|
||||
- name: Install dependencies on MacOS
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: brew install meson openssl brotli googletest
|
||||
|
||||
- name: Setup MSVC on Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
# It is necessary to remove MinGW and StrawberryPerl as they both provide
|
||||
# GCC. This causes issues because CMake prefers to use MSVC, while Meson
|
||||
# uses GCC, if found, causing linking errors.
|
||||
- name: Install dependencies on Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
choco uninstall mingw strawberryperl --yes --all-versions --remove-dependencies --skip-autouninstaller --no-color
|
||||
Remove-Item -Path C:\Strawberry -Recurse
|
||||
choco install pkgconfiglite --yes --skip-virus-check --no-color
|
||||
pip install meson ninja
|
||||
Invoke-WebRequest -Uri https://github.com/google/googletest/archive/refs/heads/master.zip -OutFile googletest-master.zip
|
||||
Expand-Archive -Path googletest-master.zip
|
||||
cd googletest-master\googletest-master
|
||||
cmake -S . -B build -DINSTALL_GTEST=ON -DBUILD_GMOCK=OFF -Dgtest_hide_internal_symbols=ON -DCMAKE_INSTALL_PREFIX=C:/googletest
|
||||
cmake --build build --config=Release
|
||||
cmake --install build --config=Release
|
||||
cd ..\..
|
||||
|
||||
- name: Build and test
|
||||
run: |
|
||||
meson setup build -Dcpp-httplib_test=true -Dpkg_config_path=C:\googletest\lib\pkgconfig -Db_vscrt=static_from_buildtype
|
||||
meson test --no-stdsplit --print-errorlogs -C build
|
||||
|
||||
@@ -787,6 +787,8 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
|
||||
#include <httplib.h>
|
||||
```
|
||||
|
||||
Note: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance.
|
||||
|
||||
Note: Windows 8 or lower and Cygwin on Windows are not supported.
|
||||
|
||||
License
|
||||
|
||||
@@ -259,7 +259,7 @@ namespace detail {
|
||||
|
||||
template <class T, class... Args>
|
||||
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
|
||||
make_unique(Args &&... args) {
|
||||
make_unique(Args &&...args) {
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
@@ -492,7 +492,7 @@ public:
|
||||
virtual socket_t socket() const = 0;
|
||||
|
||||
template <typename... Args>
|
||||
ssize_t write_format(const char *fmt, const Args &... args);
|
||||
ssize_t write_format(const char *fmt, const Args &...args);
|
||||
ssize_t write(const char *ptr);
|
||||
ssize_t write(const std::string &s);
|
||||
};
|
||||
@@ -622,7 +622,7 @@ public:
|
||||
Server &Options(const std::string &pattern, Handler handler);
|
||||
|
||||
bool set_base_dir(const std::string &dir,
|
||||
const std::string &mount_point = nullptr);
|
||||
const std::string &mount_point = std::string());
|
||||
bool set_mount_point(const std::string &mount_point, const std::string &dir,
|
||||
Headers headers = Headers());
|
||||
bool remove_mount_point(const std::string &mount_point);
|
||||
@@ -1161,6 +1161,8 @@ public:
|
||||
const std::string &client_cert_path,
|
||||
const std::string &client_key_path);
|
||||
|
||||
Client(Client &&) = default;
|
||||
|
||||
~Client();
|
||||
|
||||
bool is_valid() const;
|
||||
@@ -1473,7 +1475,7 @@ inline T Response::get_header_value(const char *key, size_t id) const {
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline ssize_t Stream::write_format(const char *fmt, const Args &... args) {
|
||||
inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
|
||||
const auto bufsiz = 2048;
|
||||
std::array<char, bufsiz> buf;
|
||||
|
||||
@@ -1671,6 +1673,10 @@ bool parse_range_header(const std::string &s, Ranges &ranges);
|
||||
|
||||
int close_socket(socket_t sock);
|
||||
|
||||
ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);
|
||||
|
||||
ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);
|
||||
|
||||
enum class EncodingType { None = 0, Gzip, Brotli };
|
||||
|
||||
EncodingType encoding_type(const Request &req, const Response &res);
|
||||
@@ -2189,6 +2195,34 @@ template <typename T> inline ssize_t handle_EINTR(T fn) {
|
||||
return res;
|
||||
}
|
||||
|
||||
inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) {
|
||||
return handle_EINTR([&]() {
|
||||
return recv(sock,
|
||||
#ifdef _WIN32
|
||||
static_cast<char *>(ptr),
|
||||
static_cast<int>(size),
|
||||
#else
|
||||
ptr,
|
||||
size,
|
||||
#endif
|
||||
flags);
|
||||
});
|
||||
}
|
||||
|
||||
inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags) {
|
||||
return handle_EINTR([&]() {
|
||||
return send(sock,
|
||||
#ifdef _WIN32
|
||||
static_cast<const char *>(ptr),
|
||||
static_cast<int>(size),
|
||||
#else
|
||||
ptr,
|
||||
size,
|
||||
#endif
|
||||
flags);
|
||||
});
|
||||
}
|
||||
|
||||
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
struct pollfd pfd_read;
|
||||
@@ -2313,6 +2347,12 @@ private:
|
||||
time_t read_timeout_usec_;
|
||||
time_t write_timeout_sec_;
|
||||
time_t write_timeout_usec_;
|
||||
|
||||
std::vector<char> read_buff_;
|
||||
size_t read_buff_off_ = 0;
|
||||
size_t read_buff_content_size_ = 0;
|
||||
|
||||
static const size_t read_buff_size_ = 1024 * 4;
|
||||
};
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
@@ -2860,10 +2900,10 @@ inline bool gzip_compressor::compress(const char *data, size_t data_length,
|
||||
|
||||
do {
|
||||
constexpr size_t max_avail_in =
|
||||
std::numeric_limits<decltype(strm_.avail_in)>::max();
|
||||
(std::numeric_limits<decltype(strm_.avail_in)>::max)();
|
||||
|
||||
strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
|
||||
std::min(data_length, max_avail_in));
|
||||
(std::min)(data_length, max_avail_in));
|
||||
strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
|
||||
|
||||
data_length -= strm_.avail_in;
|
||||
@@ -2919,10 +2959,10 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
|
||||
|
||||
do {
|
||||
constexpr size_t max_avail_in =
|
||||
std::numeric_limits<decltype(strm_.avail_in)>::max();
|
||||
(std::numeric_limits<decltype(strm_.avail_in)>::max)();
|
||||
|
||||
strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
|
||||
std::min(data_length, max_avail_in));
|
||||
(std::min)(data_length, max_avail_in));
|
||||
strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
|
||||
|
||||
data_length -= strm_.avail_in;
|
||||
@@ -2933,7 +2973,14 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
|
||||
strm_.avail_out = static_cast<uInt>(buff.size());
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
|
||||
auto prev_avail_in = strm_.avail_in;
|
||||
|
||||
ret = inflate(&strm_, Z_NO_FLUSH);
|
||||
|
||||
if (prev_avail_in - strm_.avail_in == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(ret != Z_STREAM_ERROR);
|
||||
switch (ret) {
|
||||
case Z_NEED_DICT:
|
||||
@@ -3957,17 +4004,15 @@ template <typename CTX, typename Init, typename Update, typename Final>
|
||||
inline std::string message_digest(const std::string &s, Init init,
|
||||
Update update, Final final,
|
||||
size_t digest_length) {
|
||||
using namespace std;
|
||||
|
||||
std::vector<unsigned char> md(digest_length, 0);
|
||||
CTX ctx;
|
||||
init(&ctx);
|
||||
update(&ctx, s.data(), s.size());
|
||||
final(md.data(), &ctx);
|
||||
|
||||
stringstream ss;
|
||||
std::stringstream ss;
|
||||
for (auto c : md) {
|
||||
ss << setfill('0') << setw(2) << hex << (unsigned int)c;
|
||||
ss << std::setfill('0') << std::setw(2) << std::hex << (unsigned int)c;
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
@@ -4035,45 +4080,55 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
|
||||
const Request &req, const std::map<std::string, std::string> &auth,
|
||||
size_t cnonce_count, const std::string &cnonce, const std::string &username,
|
||||
const std::string &password, bool is_proxy = false) {
|
||||
using namespace std;
|
||||
|
||||
string nc;
|
||||
std::string nc;
|
||||
{
|
||||
stringstream ss;
|
||||
ss << setfill('0') << setw(8) << hex << cnonce_count;
|
||||
std::stringstream ss;
|
||||
ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
|
||||
nc = ss.str();
|
||||
}
|
||||
|
||||
auto qop = auth.at("qop");
|
||||
if (qop.find("auth-int") != std::string::npos) {
|
||||
qop = "auth-int";
|
||||
} else {
|
||||
qop = "auth";
|
||||
std::string qop;
|
||||
if (auth.find("qop") != auth.end()) {
|
||||
qop = auth.at("qop");
|
||||
if (qop.find("auth-int") != std::string::npos) {
|
||||
qop = "auth-int";
|
||||
} else if (qop.find("auth") != std::string::npos) {
|
||||
qop = "auth";
|
||||
} else {
|
||||
qop.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::string algo = "MD5";
|
||||
if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
|
||||
|
||||
string response;
|
||||
std::string response;
|
||||
{
|
||||
auto H = algo == "SHA-256"
|
||||
? detail::SHA_256
|
||||
: algo == "SHA-512" ? detail::SHA_512 : detail::MD5;
|
||||
auto H = algo == "SHA-256" ? detail::SHA_256
|
||||
: algo == "SHA-512" ? detail::SHA_512
|
||||
: detail::MD5;
|
||||
|
||||
auto A1 = username + ":" + auth.at("realm") + ":" + password;
|
||||
|
||||
auto A2 = req.method + ":" + req.path;
|
||||
if (qop == "auth-int") { A2 += ":" + H(req.body); }
|
||||
|
||||
response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
|
||||
":" + qop + ":" + H(A2));
|
||||
if (qop.empty()) {
|
||||
response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
|
||||
} else {
|
||||
response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
|
||||
":" + qop + ":" + H(A2));
|
||||
}
|
||||
}
|
||||
|
||||
auto field = "Digest username=\"" + username + "\", realm=\"" +
|
||||
auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
|
||||
"\", uri=\"" + req.path + "\", algorithm=" + algo +
|
||||
", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce +
|
||||
"\", response=\"" + response + "\"";
|
||||
auto field =
|
||||
"Digest username=\"" + username + "\", realm=\"" + auth.at("realm") +
|
||||
"\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path +
|
||||
"\", algorithm=" + algo +
|
||||
(qop.empty() ? ", response=\""
|
||||
: ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" +
|
||||
cnonce + "\", response=\"") +
|
||||
response + "\"";
|
||||
|
||||
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
|
||||
return std::make_pair(key, field);
|
||||
@@ -4360,7 +4415,8 @@ inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
|
||||
: sock_(sock), read_timeout_sec_(read_timeout_sec),
|
||||
read_timeout_usec_(read_timeout_usec),
|
||||
write_timeout_sec_(write_timeout_sec),
|
||||
write_timeout_usec_(write_timeout_usec) {}
|
||||
write_timeout_usec_(write_timeout_usec),
|
||||
read_buff_(read_buff_size_, 0) {}
|
||||
|
||||
inline SocketStream::~SocketStream() {}
|
||||
|
||||
@@ -4373,31 +4429,56 @@ inline bool SocketStream::is_writable() const {
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
#ifdef _WIN32
|
||||
size = (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
#else
|
||||
size = (std::min)(size, static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
|
||||
#endif
|
||||
|
||||
if (read_buff_off_ < read_buff_content_size_) {
|
||||
auto remaining_size = read_buff_content_size_ - read_buff_off_;
|
||||
if (size <= remaining_size) {
|
||||
memcpy(ptr, read_buff_.data() + read_buff_off_, size);
|
||||
read_buff_off_ += size;
|
||||
return static_cast<ssize_t>(size);
|
||||
} else {
|
||||
memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size);
|
||||
read_buff_off_ += remaining_size;
|
||||
return static_cast<ssize_t>(remaining_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_readable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
|
||||
return -1;
|
||||
read_buff_off_ = 0;
|
||||
read_buff_content_size_ = 0;
|
||||
|
||||
if (size < read_buff_size_) {
|
||||
auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, CPPHTTPLIB_RECV_FLAGS);
|
||||
if (n <= 0) {
|
||||
return n;
|
||||
} else if (n <= static_cast<ssize_t>(size)) {
|
||||
memcpy(ptr, read_buff_.data(), static_cast<size_t>(n));
|
||||
return n;
|
||||
} else {
|
||||
memcpy(ptr, read_buff_.data(), size);
|
||||
read_buff_off_ = size;
|
||||
read_buff_content_size_ = static_cast<size_t>(n);
|
||||
return static_cast<ssize_t>(size);
|
||||
}
|
||||
} else {
|
||||
return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
|
||||
}
|
||||
return recv(sock_, ptr, static_cast<int>(size), CPPHTTPLIB_RECV_FLAGS);
|
||||
#else
|
||||
return handle_EINTR(
|
||||
[&]() { return recv(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); });
|
||||
#endif
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::write(const char *ptr, size_t size) {
|
||||
if (!is_writable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
|
||||
return -1;
|
||||
}
|
||||
return send(sock_, ptr, static_cast<int>(size), CPPHTTPLIB_SEND_FLAGS);
|
||||
#else
|
||||
return handle_EINTR(
|
||||
[&]() { return send(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); });
|
||||
size = (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
#endif
|
||||
|
||||
return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
|
||||
}
|
||||
|
||||
inline void SocketStream::get_remote_ip_and_port(std::string &ip,
|
||||
@@ -4922,7 +5003,7 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
|
||||
/* For debug
|
||||
size_t pos = 0;
|
||||
while (pos < n) {
|
||||
auto read_size = std::min<size_t>(1, n - pos);
|
||||
auto read_size = (std::min)<size_t>(1, n - pos);
|
||||
auto ret = multipart_form_data_parser.parse(
|
||||
buf + pos, read_size, multipart_receiver, mulitpart_header);
|
||||
if (!ret) { return false; }
|
||||
@@ -5941,7 +6022,12 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
return write_content_with_provider(strm, req, error);
|
||||
}
|
||||
|
||||
return detail::write_data(strm, req.body.data(), req.body.size());
|
||||
if (!detail::write_data(strm, req.body.data(), req.body.size())) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
|
||||
+108
-2
@@ -1,7 +1,113 @@
|
||||
project('cpp-httplib', 'cpp', license: 'MIT')
|
||||
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
cpp_httplib_dep = declare_dependency(include_directories: include_directories('.'))
|
||||
project(
|
||||
'cpp-httplib',
|
||||
'cpp',
|
||||
license: 'MIT',
|
||||
default_options: [
|
||||
'cpp_std=c++11',
|
||||
'buildtype=release',
|
||||
'b_ndebug=if-release',
|
||||
'b_lto=true',
|
||||
'warning_level=3'
|
||||
],
|
||||
meson_version: '>=0.47.0'
|
||||
)
|
||||
|
||||
# Check just in case downstream decides to edit the source
|
||||
# and add a project version
|
||||
version = meson.project_version()
|
||||
if version == 'undefined'
|
||||
git = find_program('git', required: false)
|
||||
if git.found()
|
||||
result = run_command(git, 'describe', '--tags', '--abbrev=0')
|
||||
if result.returncode() == 0
|
||||
version = result.stdout().strip('v\n')
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
python = import('python').find_installation('python3')
|
||||
# If version is still undefined it means that the git method failed
|
||||
if version == 'undefined'
|
||||
# Meson doesn't have regular expressions, but since it is implemented
|
||||
# in python we can be sure we can use it to parse the file manually
|
||||
version = run_command(
|
||||
python, '-c', 'import re; raw_version = re.search("User\-Agent.*cpp\-httplib/([0-9]+\.?)+", open("httplib.h").read()).group(0); print(re.search("([0-9]+\\.?)+", raw_version).group(0))'
|
||||
).stdout().strip()
|
||||
endif
|
||||
|
||||
message('cpp-httplib version ' + version)
|
||||
|
||||
deps = [dependency('threads')]
|
||||
args = []
|
||||
|
||||
openssl_dep = dependency('openssl', version: ['>=1.1.1', '<1.1.2'], required: get_option('cpp-httplib_openssl'))
|
||||
if openssl_dep.found()
|
||||
deps += openssl_dep
|
||||
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
|
||||
endif
|
||||
|
||||
zlib_dep = dependency('zlib', required: get_option('cpp-httplib_zlib'))
|
||||
if zlib_dep.found()
|
||||
deps += zlib_dep
|
||||
args += '-DCPPHTTPLIB_ZLIB_SUPPORT'
|
||||
endif
|
||||
|
||||
brotli_deps = [dependency('libbrotlicommon', required: get_option('cpp-httplib_brotli'))]
|
||||
brotli_deps += dependency('libbrotlidec', required: get_option('cpp-httplib_brotli'))
|
||||
brotli_deps += dependency('libbrotlienc', required: get_option('cpp-httplib_brotli'))
|
||||
|
||||
brotli_found_all = true
|
||||
foreach brotli_dep : brotli_deps
|
||||
if not brotli_dep.found()
|
||||
brotli_found_all = false
|
||||
endif
|
||||
endforeach
|
||||
|
||||
if brotli_found_all
|
||||
deps += brotli_deps
|
||||
args += '-DCPPHTTPLIB_BROTLI_SUPPORT'
|
||||
endif
|
||||
|
||||
cpp_httplib_dep = dependency('', required: false)
|
||||
|
||||
if get_option('cpp-httplib_compile')
|
||||
httplib_ch = custom_target(
|
||||
'split',
|
||||
input: 'httplib.h',
|
||||
output: ['httplib.cc', 'httplib.h'],
|
||||
command: [python, files('split.py'), '--out', meson.current_build_dir()],
|
||||
install: true,
|
||||
install_dir: [false, get_option('includedir')]
|
||||
)
|
||||
lib = library(
|
||||
'cpp-httplib',
|
||||
sources: httplib_ch,
|
||||
dependencies: deps,
|
||||
cpp_args: args,
|
||||
version: version,
|
||||
install: true
|
||||
)
|
||||
cpp_httplib_dep = declare_dependency(link_with: lib, sources: httplib_ch[1])
|
||||
|
||||
import('pkgconfig').generate(
|
||||
lib,
|
||||
description: 'A C++ HTTP/HTTPS server and client library',
|
||||
url: 'https://github.com/yhirose/cpp-httplib',
|
||||
version: version
|
||||
)
|
||||
else
|
||||
install_headers('httplib.h')
|
||||
cpp_httplib_dep = declare_dependency(include_directories: include_directories('.'), dependencies: deps)
|
||||
endif
|
||||
|
||||
if meson.version().version_compare('>=0.54.0')
|
||||
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
|
||||
endif
|
||||
|
||||
if get_option('cpp-httplib_test')
|
||||
subdir('test')
|
||||
endif
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
option('cpp-httplib_openssl', type: 'feature', value: 'auto', description: 'Enable OpenSSL support')
|
||||
option('cpp-httplib_zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
|
||||
option('cpp-httplib_brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
|
||||
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires Python 3)')
|
||||
option('cpp-httplib_test', type: 'boolean', value: false, description: 'Build tests')
|
||||
@@ -19,29 +19,49 @@ args_parser.add_argument(
|
||||
args = args_parser.parse_args()
|
||||
|
||||
cur_dir = os.path.dirname(sys.argv[0])
|
||||
with open(cur_dir + '/httplib.h') as f:
|
||||
lines = f.readlines()
|
||||
lib_name = 'httplib'
|
||||
header_name = '/' + lib_name + '.h'
|
||||
source_name = '/' + lib_name + '.' + args.extension
|
||||
# get the input file
|
||||
in_file = cur_dir + header_name
|
||||
# get the output file
|
||||
h_out = args.out + header_name
|
||||
cc_out = args.out + source_name
|
||||
|
||||
python_version = sys.version_info[0]
|
||||
if python_version < 3:
|
||||
os.makedirs(args.out)
|
||||
# if the modification time of the out file is after the in file,
|
||||
# don't split (as it is already finished)
|
||||
do_split = True
|
||||
|
||||
if os.path.exists(h_out):
|
||||
in_time = os.path.getmtime(in_file)
|
||||
out_time = os.path.getmtime(h_out)
|
||||
do_split = in_time > out_time
|
||||
|
||||
if do_split:
|
||||
with open(in_file) as f:
|
||||
lines = f.readlines()
|
||||
|
||||
python_version = sys.version_info[0]
|
||||
if python_version < 3:
|
||||
os.makedirs(args.out)
|
||||
else:
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
|
||||
in_implementation = False
|
||||
cc_out = args.out + source_name
|
||||
with open(h_out, 'w') as fh, open(cc_out, 'w') as fc:
|
||||
fc.write('#include "httplib.h"\n')
|
||||
fc.write('namespace httplib {\n')
|
||||
for line in lines:
|
||||
is_border_line = border in line
|
||||
if is_border_line:
|
||||
in_implementation = not in_implementation
|
||||
elif in_implementation:
|
||||
fc.write(line.replace('inline ', ''))
|
||||
else:
|
||||
fh.write(line)
|
||||
fc.write('} // namespace httplib\n')
|
||||
|
||||
print("Wrote {} and {}".format(h_out, cc_out))
|
||||
else:
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
|
||||
in_implementation = False
|
||||
h_out = args.out + '/httplib.h'
|
||||
cc_out = args.out + '/httplib.' + args.extension
|
||||
with open(h_out, 'w') as fh, open(cc_out, 'w') as fc:
|
||||
fc.write('#include "httplib.h"\n')
|
||||
fc.write('namespace httplib {\n')
|
||||
for line in lines:
|
||||
is_border_line = border in line
|
||||
if is_border_line:
|
||||
in_implementation = not in_implementation
|
||||
elif in_implementation:
|
||||
fc.write(line.replace('inline ', ''))
|
||||
else:
|
||||
fh.write(line)
|
||||
fc.write('} // namespace httplib\n')
|
||||
|
||||
print("Wrote {} and {}".format(h_out, cc_out))
|
||||
print("{} and {} are up to date".format(h_out, cc_out))
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
gtest_dep = dependency('gtest', main: true)
|
||||
openssl = find_program('openssl')
|
||||
test_conf = files('test.conf')
|
||||
|
||||
key_pem = custom_target(
|
||||
'key_pem',
|
||||
output: 'key.pem',
|
||||
command: [openssl, 'genrsa', '-out', '@OUTPUT@', '2048']
|
||||
)
|
||||
|
||||
temp_req = custom_target(
|
||||
'temp_req',
|
||||
input: key_pem,
|
||||
output: 'temp_req',
|
||||
command: [openssl, 'req', '-new', '-batch', '-config', test_conf, '-key', '@INPUT@', '-out', '@OUTPUT@']
|
||||
)
|
||||
|
||||
cert_pem = custom_target(
|
||||
'cert_pem',
|
||||
input: [temp_req, key_pem],
|
||||
output: 'cert.pem',
|
||||
command: [openssl, 'x509', '-in', '@INPUT0@', '-days', '3650', '-req', '-signkey', '@INPUT1@', '-out', '@OUTPUT@']
|
||||
)
|
||||
|
||||
cert2_pem = custom_target(
|
||||
'cert2_pem',
|
||||
input: key_pem,
|
||||
output: 'cert2.pem',
|
||||
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
|
||||
)
|
||||
|
||||
rootca_key_pem = custom_target(
|
||||
'rootca_key_pem',
|
||||
output: 'rootCA.key.pem',
|
||||
command: [openssl, 'genrsa', '-out', '@OUTPUT@', '2048']
|
||||
)
|
||||
|
||||
rootca_cert_pem = custom_target(
|
||||
'rootca_cert_pem',
|
||||
input: rootca_key_pem,
|
||||
output: 'rootCA.cert.pem',
|
||||
command: [openssl, 'req', '-x509', '-new', '-batch', '-config', files('test.rootCA.conf'), '-key', '@INPUT@', '-days', '1024', '-out', '@OUTPUT@']
|
||||
)
|
||||
|
||||
client_key_pem = custom_target(
|
||||
'client_key_pem',
|
||||
output: 'client.key.pem',
|
||||
command: [openssl, 'genrsa', '-out', '@OUTPUT@', '2048']
|
||||
)
|
||||
|
||||
client_temp_req = custom_target(
|
||||
'client_temp_req',
|
||||
input: client_key_pem,
|
||||
output: 'client_temp_req',
|
||||
command: [openssl, 'req', '-new', '-batch', '-config', test_conf, '-key', '@INPUT@', '-out', '@OUTPUT@']
|
||||
)
|
||||
|
||||
client_cert_pem = custom_target(
|
||||
'client_cert_pem',
|
||||
input: [client_temp_req, rootca_cert_pem, rootca_key_pem],
|
||||
output: 'client.cert.pem',
|
||||
command: [openssl, 'x509', '-in', '@INPUT0@', '-days', '370', '-req', '-CA', '@INPUT1@', '-CAkey', '@INPUT2@', '-CAcreateserial', '-out', '@OUTPUT@']
|
||||
)
|
||||
|
||||
# Copy test files to the build directory
|
||||
configure_file(input: 'ca-bundle.crt', output: 'ca-bundle.crt', copy: true)
|
||||
configure_file(input: 'image.jpg', output: 'image.jpg', copy: true)
|
||||
subdir(join_paths('www', 'dir'))
|
||||
subdir(join_paths('www2', 'dir'))
|
||||
subdir(join_paths('www3', 'dir'))
|
||||
|
||||
test(
|
||||
'main',
|
||||
executable(
|
||||
'main',
|
||||
'test.cc',
|
||||
dependencies: [
|
||||
cpp_httplib_dep,
|
||||
gtest_dep
|
||||
]
|
||||
),
|
||||
depends: [
|
||||
key_pem,
|
||||
cert_pem,
|
||||
cert2_pem,
|
||||
rootca_key_pem,
|
||||
rootca_cert_pem,
|
||||
client_key_pem,
|
||||
client_cert_pem
|
||||
],
|
||||
workdir: meson.current_build_dir(),
|
||||
timeout: 300
|
||||
)
|
||||
+22
-3
@@ -8,6 +8,7 @@
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
|
||||
#define SERVER_CERT_FILE "./cert.pem"
|
||||
#define SERVER_CERT2_FILE "./cert2.pem"
|
||||
@@ -40,6 +41,11 @@ MultipartFormData &get_file_value(MultipartFormDataItems &files,
|
||||
throw std::runtime_error("invalid mulitpart form data name error");
|
||||
}
|
||||
|
||||
TEST(ConstructorTest, MoveConstructible) {
|
||||
EXPECT_FALSE(std::is_copy_constructible<Client>::value);
|
||||
EXPECT_TRUE(std::is_nothrow_move_constructible<Client>::value);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST(StartupTest, WSAStartup) {
|
||||
WSADATA wsaData;
|
||||
@@ -971,8 +977,15 @@ TEST(RedirectFromPageWithContentIP6, Redirect) {
|
||||
|
||||
auto th = std::thread([&]() { svr.listen("::1", 1234); });
|
||||
|
||||
while (!svr.is_running()) {
|
||||
// When IPV6 support isn't available svr.listen("::1", 1234) never
|
||||
// actually starts anything, so the condition !svr.is_running() will
|
||||
// always remain true, and the loop never stops.
|
||||
// This basically counts how many milliseconds have passed since the
|
||||
// call to svr.listen(), and if after 5 seconds nothing started yet
|
||||
// aborts the test.
|
||||
for (unsigned int milliseconds = 0; !svr.is_running(); milliseconds++) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
ASSERT_LT(milliseconds, 5000U);
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
@@ -1349,11 +1362,13 @@ protected:
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
res.set_content("slow", "text/plain");
|
||||
})
|
||||
#if 0
|
||||
.Post("/slowpost",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
res.set_content("slow", "text/plain");
|
||||
})
|
||||
#endif
|
||||
.Get("/remote_addr",
|
||||
[&](const Request &req, Response &res) {
|
||||
auto remote_addr = req.headers.find("REMOTE_ADDR")->second;
|
||||
@@ -2623,6 +2638,7 @@ TEST_F(ServerTest, SlowRequest) {
|
||||
std::thread([=]() { auto res = cli_.Get("/slow"); }));
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_F(ServerTest, SlowPost) {
|
||||
char buffer[64 * 1024];
|
||||
memset(buffer, 0x42, sizeof(buffer));
|
||||
@@ -2656,6 +2672,7 @@ TEST_F(ServerTest, SlowPostFail) {
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::Write, res.error());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(ServerTest, Put) {
|
||||
auto res = cli_.Put("/put", "PUT", "text/plain");
|
||||
@@ -3562,10 +3579,12 @@ TEST(StreamingTest, NoContentLengthStreaming) {
|
||||
Client client(HOST, PORT);
|
||||
|
||||
auto get_thread = std::thread([&client]() {
|
||||
auto res = client.Get("/stream", [](const char *data, size_t len) -> bool {
|
||||
EXPECT_EQ("aaabbb", std::string(data, len));
|
||||
std::string s;
|
||||
auto res = client.Get("/stream", [&s](const char *data, size_t len) -> bool {
|
||||
s += std::string(data, len);
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ("aaabbb", s);
|
||||
});
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
configure_file(input: 'index.html', output: 'index.html', copy: true)
|
||||
configure_file(input: 'test.abcde', output: 'test.abcde', copy: true)
|
||||
configure_file(input: 'test.html', output: 'test.html', copy: true)
|
||||
@@ -0,0 +1,6 @@
|
||||
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
configure_file(input: 'index.html', output: 'index.html', copy: true)
|
||||
configure_file(input: 'test.html', output: 'test.html', copy: true)
|
||||
@@ -0,0 +1,6 @@
|
||||
# SPDX-FileCopyrightText: 2021 Andrea Pappacoda
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
configure_file(input: 'index.html', output: 'index.html', copy: true)
|
||||
configure_file(input: 'test.html', output: 'test.html', copy: true)
|
||||
Reference in New Issue
Block a user