mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 530d6ee098 | |||
| 420c9759c6 | |||
| 2ce7c22218 | |||
| 4ef9ed80cd | |||
| 44b3fe6277 | |||
| 449801990f | |||
| af2928d316 | |||
| d948e38820 | |||
| 65218ce222 | |||
| 55e99c4030 | |||
| b63d50671d | |||
| eba980846b | |||
| 374d058de7 | |||
| 31cdcc3c3a | |||
| ad9f6423e2 |
+31
-38
@@ -3,11 +3,11 @@
|
||||
* BUILD_SHARED_LIBS (default off) builds as a shared library (if HTTPLIB_COMPILE is ON)
|
||||
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_REQUIRE_OPENSSL (default off)
|
||||
* HTTPLIB_REQUIRE_ZLIB (default off)
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
|
||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_INSTALL (default on)
|
||||
* HTTPLIB_TEST (default off)
|
||||
@@ -59,7 +59,6 @@
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
FindPython3 requires Cmake v3.12
|
||||
ARCH_INDEPENDENT option of write_basic_package_version_file() requires Cmake v3.14
|
||||
]]
|
||||
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
@@ -69,20 +68,19 @@ cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
# This is so the maintainer doesn't actually need to update this manually.
|
||||
file(STRINGS httplib.h _raw_version_string REGEX "CPPHTTPLIB_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+)\"")
|
||||
|
||||
# Needed since git tags have "v" prefixing them.
|
||||
# Also used if the fallback to user agent string is being used.
|
||||
# Extracts just the version string itself from the whole string contained in _raw_version_string
|
||||
# since _raw_version_string would contain the entire line of code where it found the version string
|
||||
string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}")
|
||||
|
||||
project(httplib VERSION ${_httplib_version} LANGUAGES CXX)
|
||||
|
||||
# Change as needed to set an OpenSSL minimum version.
|
||||
# This is used in the installed Cmake config file.
|
||||
set(_HTTPLIB_OPENSSL_MIN_VER "3.0.0")
|
||||
|
||||
# Lets you disable C++ exception during CMake configure time.
|
||||
# The value is used in the install CMake config file.
|
||||
option(HTTPLIB_NO_EXCEPTIONS "Disable the use of C++ exceptions" OFF)
|
||||
|
||||
# Change as needed to set an OpenSSL minimum version.
|
||||
# This is used in the installed Cmake config file.
|
||||
set(_HTTPLIB_OPENSSL_MIN_VER "1.1.1")
|
||||
|
||||
# Allow for a build to require OpenSSL to pass, instead of just being optional
|
||||
option(HTTPLIB_REQUIRE_OPENSSL "Requires OpenSSL to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build." OFF)
|
||||
@@ -94,10 +92,6 @@ option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib co
|
||||
option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF)
|
||||
# Lets you disable the installation (useful when fetched from another CMake project)
|
||||
option(HTTPLIB_INSTALL "Enables the installation target" ON)
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(HTTPLIB_COMPILE)
|
||||
set(HTTPLIB_IS_COMPILED TRUE)
|
||||
endif()
|
||||
option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
|
||||
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
|
||||
@@ -110,45 +104,48 @@ if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
endif()
|
||||
|
||||
# Set some variables that are used in-tree and while building based on our options
|
||||
set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE})
|
||||
set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN ${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN})
|
||||
|
||||
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
|
||||
set(THREADS_PREFER_PTHREAD_FLAG true)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
# Since Cmake v3.11, Crypto & SSL became optional when not specified as COMPONENTS.
|
||||
if(HTTPLIB_REQUIRE_OPENSSL)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL REQUIRED)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(OPENSSL_FOUND AND NOT DEFINED HTTPLIB_IS_USING_OPENSSL)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
# Avoid a rare circumstance of not finding all components but the end-user did their
|
||||
# own call for OpenSSL, which might trick us into thinking we'd otherwise have what we wanted
|
||||
if (TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto)
|
||||
set(HTTPLIB_IS_USING_OPENSSL ${OPENSSL_FOUND})
|
||||
else()
|
||||
set(HTTPLIB_IS_USING_OPENSSL FALSE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_REQUIRE_ZLIB)
|
||||
find_package(ZLIB REQUIRED)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
elseif(HTTPLIB_USE_ZLIB_IF_AVAILABLE)
|
||||
find_package(ZLIB QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Adds our cmake folder to the search path for find_package
|
||||
# This is so we can use our custom FindBrotli.cmake
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
if(HTTPLIB_REQUIRE_BROTLI)
|
||||
find_package(Brotli COMPONENTS encoder decoder common REQUIRED)
|
||||
set(HTTPLIB_IS_USING_BROTLI TRUE)
|
||||
elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE)
|
||||
find_package(Brotli COMPONENTS encoder decoder common QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(Brotli_FOUND)
|
||||
set(HTTPLIB_IS_USING_BROTLI TRUE)
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
|
||||
set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN TRUE)
|
||||
set(HTTPLIB_IS_USING_BROTLI ${Brotli_FOUND})
|
||||
endif()
|
||||
|
||||
# Used for default, common dirs that the end-user can change (if needed)
|
||||
@@ -204,9 +201,7 @@ endif()
|
||||
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
# Require C++11
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
cxx_std_11
|
||||
)
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
||||
@@ -273,9 +268,7 @@ if(HTTPLIB_INSTALL)
|
||||
# Creates the export httplibTargets.cmake
|
||||
# This is strictly what holds compilation requirements
|
||||
# and linkage information (doesn't find deps though).
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT httplibTargets
|
||||
)
|
||||
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets)
|
||||
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ SSL Support
|
||||
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0.
|
||||
NOTE: cpp-httplib currently supports only version 3.0 or later. Please see [this page](https://www.openssl.org/policies/releasestrat.html) to get more information.
|
||||
|
||||
NOTE for macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`.
|
||||
|
||||
@@ -433,6 +433,17 @@ If you want to set the thread count at runtime, there is no convenient way... Bu
|
||||
svr.new_task_queue = [] { return new ThreadPool(12); };
|
||||
```
|
||||
|
||||
You can also provide an optional parameter to limit the maximum number
|
||||
of pending requests, i.e. requests `accept()`ed by the listener but
|
||||
still waiting to be serviced by worker threads.
|
||||
|
||||
```cpp
|
||||
svr.new_task_queue = [] { return new ThreadPool(/*num_threads=*/12, /*max_queued_requests=*/18); };
|
||||
```
|
||||
|
||||
Default limit is 0 (unlimited). Once the limit is reached, the listener
|
||||
will shutdown the client connection.
|
||||
|
||||
### Override the default thread pool with yours
|
||||
|
||||
You can supply your own thread pool implementation according to your need.
|
||||
@@ -444,8 +455,10 @@ public:
|
||||
pool_.start_with_thread_count(n);
|
||||
}
|
||||
|
||||
virtual void enqueue(std::function<void()> fn) override {
|
||||
pool_.enqueue(fn);
|
||||
virtual bool enqueue(std::function<void()> fn) override {
|
||||
/* Return true if the task was actually enqueued, or false
|
||||
* if the caller must drop the corresponding connection. */
|
||||
return pool_.enqueue(fn);
|
||||
}
|
||||
|
||||
virtual void shutdown() override {
|
||||
@@ -848,7 +861,7 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
|
||||
|
||||
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, Visual Studio 2013 or lower, and Cygwin on Windows are not supported.
|
||||
NOTE: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@ CXXFLAGS = -O2 -std=c++11 -I.. -Wall -Wextra -pthread
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ifneq ($(OS), Windows_NT)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.14.3"
|
||||
#define CPPHTTPLIB_VERSION "0.15.0"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -160,10 +160,6 @@ using ssize_t = long;
|
||||
#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
|
||||
#endif
|
||||
|
||||
#ifndef strcasecmp
|
||||
#define strcasecmp _stricmp
|
||||
#endif // strcasecmp
|
||||
|
||||
using socket_t = SOCKET;
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
|
||||
@@ -214,6 +210,7 @@ using socket_t = int;
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
#include <exception>
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
@@ -268,10 +265,8 @@ using socket_t = int;
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#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
|
||||
#if OPENSSL_VERSION_NUMBER < 0x30000000L
|
||||
#error Sorry, OpenSSL versions prior to 3.0.0 are not supported
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -601,6 +596,7 @@ struct Response {
|
||||
void set_redirect(const std::string &url, int status = StatusCode::Found_302);
|
||||
void set_content(const char *s, size_t n, const std::string &content_type);
|
||||
void set_content(const std::string &s, const std::string &content_type);
|
||||
void set_content(std::string &&s, const std::string &content_type);
|
||||
|
||||
void set_content_provider(
|
||||
size_t length, const std::string &content_type, ContentProvider provider,
|
||||
@@ -657,7 +653,7 @@ public:
|
||||
TaskQueue() = default;
|
||||
virtual ~TaskQueue() = default;
|
||||
|
||||
virtual void enqueue(std::function<void()> fn) = 0;
|
||||
virtual bool enqueue(std::function<void()> fn) = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
virtual void on_idle() {}
|
||||
@@ -665,7 +661,8 @@ public:
|
||||
|
||||
class ThreadPool : public TaskQueue {
|
||||
public:
|
||||
explicit ThreadPool(size_t n) : shutdown_(false) {
|
||||
explicit ThreadPool(size_t n, size_t mqr = 0)
|
||||
: shutdown_(false), max_queued_requests_(mqr) {
|
||||
while (n) {
|
||||
threads_.emplace_back(worker(*this));
|
||||
n--;
|
||||
@@ -675,13 +672,17 @@ public:
|
||||
ThreadPool(const ThreadPool &) = delete;
|
||||
~ThreadPool() override = default;
|
||||
|
||||
void enqueue(std::function<void()> fn) override {
|
||||
bool enqueue(std::function<void()> fn) override {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
|
||||
return false;
|
||||
}
|
||||
jobs_.push_back(std::move(fn));
|
||||
}
|
||||
|
||||
cond_.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() override {
|
||||
@@ -731,6 +732,7 @@ private:
|
||||
std::list<std::function<void()>> jobs_;
|
||||
|
||||
bool shutdown_;
|
||||
size_t max_queued_requests_ = 0;
|
||||
|
||||
std::condition_variable cond_;
|
||||
std::mutex mutex_;
|
||||
@@ -744,6 +746,8 @@ void default_socket_options(socket_t sock);
|
||||
|
||||
const char *status_message(int status);
|
||||
|
||||
std::string get_bearer_token_auth(const Request &req);
|
||||
|
||||
namespace detail {
|
||||
|
||||
class MatcherBase {
|
||||
@@ -1942,6 +1946,15 @@ inline const char *status_message(int status) {
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string get_bearer_token_auth(const Request &req) {
|
||||
if (req.has_header("Authorization")) {
|
||||
static std::string BearerHeaderPrefix = "Bearer ";
|
||||
return req.get_header_value("Authorization")
|
||||
.substr(BearerHeaderPrefix.length());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline Server &
|
||||
Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
|
||||
@@ -2400,6 +2413,7 @@ inline bool is_valid_path(const std::string &path) {
|
||||
// Read component
|
||||
auto beg = i;
|
||||
while (i < path.size() && path[i] != '/') {
|
||||
if (path[i] == '\0') { return false; }
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -2557,7 +2571,7 @@ inline std::string trim_double_quotes_copy(const std::string &s) {
|
||||
|
||||
inline void split(const char *b, const char *e, char d,
|
||||
std::function<void(const char *, const char *)> fn) {
|
||||
return split(b, e, d, std::numeric_limits<size_t>::max(), fn);
|
||||
return split(b, e, d, (std::numeric_limits<size_t>::max)(), fn);
|
||||
}
|
||||
|
||||
inline void split(const char *b, const char *e, char d, size_t m,
|
||||
@@ -2705,7 +2719,9 @@ inline bool mmap::is_open() const { return addr_ != nullptr; }
|
||||
|
||||
inline size_t mmap::size() const { return size_; }
|
||||
|
||||
inline const char *mmap::data() const { return (const char *)addr_; }
|
||||
inline const char *mmap::data() const {
|
||||
return static_cast<const char *>(addr_);
|
||||
}
|
||||
|
||||
inline void mmap::close() {
|
||||
#if defined(_WIN32)
|
||||
@@ -2790,7 +2806,7 @@ inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
if (sock >= FD_SETSIZE) { return 1; }
|
||||
if (sock >= FD_SETSIZE) { return -1; }
|
||||
#endif
|
||||
|
||||
fd_set fds;
|
||||
@@ -2818,7 +2834,7 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
if (sock >= FD_SETSIZE) { return 1; }
|
||||
if (sock >= FD_SETSIZE) { return -1; }
|
||||
#endif
|
||||
|
||||
fd_set fds;
|
||||
@@ -3925,8 +3941,8 @@ inline bool read_content_chunked(Stream &strm, T &x,
|
||||
}
|
||||
|
||||
inline bool is_chunked_transfer_encoding(const Headers &headers) {
|
||||
return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""),
|
||||
"chunked");
|
||||
return compare_case_ignore(
|
||||
get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked");
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
@@ -4621,28 +4637,32 @@ inline std::string to_lower(const char *beg, const char *end) {
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::string make_multipart_data_boundary() {
|
||||
inline std::string random_string(size_t length) {
|
||||
static const char data[] =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
// std::random_device might actually be deterministic on some
|
||||
// platforms, but due to lack of support in the c++ standard library,
|
||||
// doing better requires either some ugly hacks or breaking portability.
|
||||
std::random_device seed_gen;
|
||||
static std::random_device seed_gen;
|
||||
|
||||
// Request 128 bits of entropy for initialization
|
||||
std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
|
||||
std::mt19937 engine(seed_sequence);
|
||||
static std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(),
|
||||
seed_gen()};
|
||||
|
||||
std::string result = "--cpp-httplib-multipart-data-";
|
||||
static std::mt19937 engine(seed_sequence);
|
||||
|
||||
for (auto i = 0; i < 16; i++) {
|
||||
std::string result;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
result += data[engine() % (sizeof(data) - 1)];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string make_multipart_data_boundary() {
|
||||
return "--cpp-httplib-multipart-data-" + detail::random_string(16);
|
||||
}
|
||||
|
||||
inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
|
||||
auto valid = true;
|
||||
for (size_t i = 0; i < boundary.size(); i++) {
|
||||
@@ -4701,32 +4721,41 @@ serialize_multipart_formdata(const MultipartFormDataItems &items,
|
||||
}
|
||||
|
||||
inline std::pair<size_t, size_t>
|
||||
get_range_offset_and_length(const Request &req, size_t content_length,
|
||||
size_t index) {
|
||||
auto r = req.ranges[index];
|
||||
|
||||
if (r.first == -1 && r.second == -1) {
|
||||
get_range_offset_and_length(Range range, size_t content_length) {
|
||||
if (range.first == -1 && range.second == -1) {
|
||||
return std::make_pair(0, content_length);
|
||||
}
|
||||
|
||||
auto slen = static_cast<ssize_t>(content_length);
|
||||
|
||||
if (r.first == -1) {
|
||||
r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
|
||||
r.second = slen - 1;
|
||||
if (range.first == -1) {
|
||||
range.first = (std::max)(static_cast<ssize_t>(0), slen - range.second);
|
||||
range.second = slen - 1;
|
||||
}
|
||||
|
||||
if (r.second == -1) { r.second = slen - 1; }
|
||||
return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
|
||||
if (range.second == -1) { range.second = slen - 1; }
|
||||
return std::make_pair(range.first,
|
||||
static_cast<size_t>(range.second - range.first) + 1);
|
||||
}
|
||||
|
||||
inline std::pair<size_t, size_t>
|
||||
get_range_offset_and_length(const Request &req, size_t content_length,
|
||||
size_t index) {
|
||||
return get_range_offset_and_length(req.ranges[index], content_length);
|
||||
}
|
||||
|
||||
inline std::string
|
||||
make_content_range_header_field(const std::pair<ssize_t, ssize_t> &range,
|
||||
size_t content_length) {
|
||||
|
||||
auto ret = get_range_offset_and_length(range, content_length);
|
||||
auto st = ret.first;
|
||||
auto ed = (std::min)(st + ret.second - 1, content_length - 1);
|
||||
|
||||
std::string field = "bytes ";
|
||||
if (range.first != -1) { field += std::to_string(range.first); }
|
||||
field += std::to_string(st);
|
||||
field += "-";
|
||||
if (range.second != -1) { field += std::to_string(range.second); }
|
||||
field += std::to_string(ed);
|
||||
field += "/";
|
||||
field += std::to_string(content_length);
|
||||
return field;
|
||||
@@ -4754,9 +4783,9 @@ bool process_multipart_ranges_data(const Request &req, Response &res,
|
||||
ctoken("\r\n");
|
||||
ctoken("\r\n");
|
||||
|
||||
auto offsets = get_range_offset_and_length(req, res.content_length_, i);
|
||||
auto offset = offsets.first;
|
||||
auto length = offsets.second;
|
||||
auto ret = get_range_offset_and_length(req, res.content_length_, i);
|
||||
auto offset = ret.first;
|
||||
auto length = ret.second;
|
||||
if (!content(offset, length)) { return false; }
|
||||
ctoken("\r\n");
|
||||
}
|
||||
@@ -4819,18 +4848,6 @@ inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
|
||||
});
|
||||
}
|
||||
|
||||
inline std::pair<size_t, size_t>
|
||||
get_range_offset_and_length(const Request &req, const Response &res,
|
||||
size_t index) {
|
||||
auto r = req.ranges[index];
|
||||
|
||||
if (r.second == -1) {
|
||||
r.second = static_cast<ssize_t>(res.content_length_) - 1;
|
||||
}
|
||||
|
||||
return std::make_pair(r.first, r.second - r.first + 1);
|
||||
}
|
||||
|
||||
inline bool expect_content(const Request &req) {
|
||||
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
|
||||
req.method == "PRI" || req.method == "DELETE") {
|
||||
@@ -5117,20 +5134,6 @@ inline bool parse_www_authenticate(const Response &res,
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240
|
||||
inline std::string random_string(size_t length) {
|
||||
auto randchar = []() -> char {
|
||||
const char charset[] = "0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
const size_t max_index = (sizeof(charset) - 1);
|
||||
return charset[static_cast<size_t>(std::rand()) % max_index];
|
||||
};
|
||||
std::string str(length, 0);
|
||||
std::generate_n(str.begin(), length, randchar);
|
||||
return str;
|
||||
}
|
||||
|
||||
class ContentProviderAdapter {
|
||||
public:
|
||||
explicit ContentProviderAdapter(
|
||||
@@ -5336,6 +5339,15 @@ inline void Response::set_content(const std::string &s,
|
||||
set_content(s.data(), s.size(), content_type);
|
||||
}
|
||||
|
||||
inline void Response::set_content(std::string &&s,
|
||||
const std::string &content_type) {
|
||||
body = std::move(s);
|
||||
|
||||
auto rng = headers.equal_range("Content-Type");
|
||||
headers.erase(rng.first, rng.second);
|
||||
set_header("Content-Type", content_type);
|
||||
}
|
||||
|
||||
inline void Response::set_content_provider(
|
||||
size_t in_length, const std::string &content_type, ContentProvider provider,
|
||||
ContentProviderResourceReleaser resource_releaser) {
|
||||
@@ -6031,10 +6043,10 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
|
||||
return detail::write_content(strm, res.content_provider_, 0,
|
||||
res.content_length_, is_shutting_down);
|
||||
} else if (req.ranges.size() == 1) {
|
||||
auto offsets =
|
||||
auto ret =
|
||||
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
||||
auto offset = offsets.first;
|
||||
auto length = offsets.second;
|
||||
auto offset = ret.first;
|
||||
auto length = ret.second;
|
||||
return detail::write_content(strm, res.content_provider_, offset, length,
|
||||
is_shutting_down);
|
||||
} else {
|
||||
@@ -6323,7 +6335,11 @@ inline bool Server::listen_internal() {
|
||||
#endif
|
||||
}
|
||||
|
||||
task_queue->enqueue([this, sock]() { process_and_close_socket(sock); });
|
||||
if (!task_queue->enqueue(
|
||||
[this, sock]() { process_and_close_socket(sock); })) {
|
||||
detail::shutdown_socket(sock);
|
||||
detail::close_socket(sock);
|
||||
}
|
||||
}
|
||||
|
||||
task_queue->shutdown();
|
||||
@@ -6447,9 +6463,9 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
||||
if (req.ranges.empty()) {
|
||||
length = res.content_length_;
|
||||
} else if (req.ranges.size() == 1) {
|
||||
auto offsets =
|
||||
auto ret =
|
||||
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
||||
length = offsets.second;
|
||||
length = ret.second;
|
||||
|
||||
auto content_range = detail::make_content_range_header_field(
|
||||
req.ranges[0], res.content_length_);
|
||||
@@ -6479,10 +6495,9 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
||||
req.ranges[0], res.body.size());
|
||||
res.set_header("Content-Range", content_range);
|
||||
|
||||
auto offsets =
|
||||
detail::get_range_offset_and_length(req, res.body.size(), 0);
|
||||
auto offset = offsets.first;
|
||||
auto length = offsets.second;
|
||||
auto ret = detail::get_range_offset_and_length(req, res.body.size(), 0);
|
||||
auto offset = ret.first;
|
||||
auto length = ret.second;
|
||||
|
||||
if (offset < res.body.size()) {
|
||||
res.body = res.body.substr(offset, length);
|
||||
@@ -8689,11 +8704,11 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
return true;
|
||||
},
|
||||
[&](SSL *ssl2) {
|
||||
// NOTE: With -Wold-style-cast, this can produce a warning, since
|
||||
// SSL_set_tlsext_host_name is a macro (in OpenSSL), which contains
|
||||
// an old style cast. Short of doing compiler specific pragma's
|
||||
// here, we can't get rid of this warning. :'(
|
||||
SSL_set_tlsext_host_name(ssl2, host_.c_str());
|
||||
// NOTE: Direct call instead of using the OpenSSL macro to suppress
|
||||
// -Wold-style-cast warning
|
||||
// SSL_set_tlsext_host_name(ssl2, host_.c_str());
|
||||
SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
|
||||
static_cast<void *>(const_cast<char *>(host_.c_str())));
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ endif
|
||||
deps = [dependency('threads')]
|
||||
args = []
|
||||
|
||||
openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cpp-httplib_openssl'))
|
||||
openssl_dep = dependency('openssl', version: '>=3.0.0', required: get_option('cpp-httplib_openssl'))
|
||||
if openssl_dep.found()
|
||||
deps += openssl_dep
|
||||
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@ CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow #
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ifneq ($(OS), Windows_NT)
|
||||
|
||||
+121
-5
@@ -71,6 +71,15 @@ TEST(DecodeURLTest, PercentCharacter) {
|
||||
R"(descrip=Gastos áéíóúñÑ 6)");
|
||||
}
|
||||
|
||||
TEST(DecodeURLTest, PercentCharacterNUL) {
|
||||
string expected;
|
||||
expected.push_back('x');
|
||||
expected.push_back('\0');
|
||||
expected.push_back('x');
|
||||
|
||||
EXPECT_EQ(detail::decode_url("x%00x", false), expected);
|
||||
}
|
||||
|
||||
TEST(EncodeQueryParamTest, ParseUnescapedChararactersTest) {
|
||||
string unescapedCharacters = "-_.!~*'()";
|
||||
|
||||
@@ -2482,6 +2491,12 @@ TEST_F(ServerTest, GetMethodInvalidMountPath) {
|
||||
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethodEmbeddedNUL) {
|
||||
auto res = cli_.Get("/mount/dir/test.html%00.js");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethodOutOfBaseDirMount) {
|
||||
auto res = cli_.Get("/mount/../www2/dir/test.html");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -2525,6 +2540,7 @@ TEST_F(ServerTest, StaticFileRange) {
|
||||
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 2-3/5", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("cd"), res->body);
|
||||
}
|
||||
|
||||
@@ -2538,7 +2554,7 @@ TEST_F(ServerTest, StaticFileRanges) {
|
||||
.find(
|
||||
"multipart/byteranges; boundary=--cpp-httplib-multipart-data-") ==
|
||||
0);
|
||||
EXPECT_EQ("265", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("266", res->get_header_value("Content-Length"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, StaticFileRangeHead) {
|
||||
@@ -2548,6 +2564,7 @@ TEST_F(ServerTest, StaticFileRangeHead) {
|
||||
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 2-3/5", res->get_header_value("Content-Range"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, StaticFileRangeBigFile) {
|
||||
@@ -2557,6 +2574,8 @@ TEST_F(ServerTest, StaticFileRangeBigFile) {
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("5", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 1048571-1048575/1048576",
|
||||
res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ("LAST\n", res->body);
|
||||
}
|
||||
|
||||
@@ -2567,6 +2586,7 @@ TEST_F(ServerTest, StaticFileRangeBigFile2) {
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("4097", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 1-4097/1048576", res->get_header_value("Content-Range"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, StaticFileBigFile) {
|
||||
@@ -2893,6 +2913,8 @@ TEST_F(ServerTest, GetStreamed2) {
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 2-3/6", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("ab"), res->body);
|
||||
}
|
||||
|
||||
@@ -2910,6 +2932,7 @@ TEST_F(ServerTest, GetStreamedWithRange1) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("3", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 3-5/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("def"), res->body);
|
||||
}
|
||||
|
||||
@@ -2919,6 +2942,7 @@ TEST_F(ServerTest, GetStreamedWithRange2) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("6", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 1-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("bcdefg"), res->body);
|
||||
}
|
||||
|
||||
@@ -2928,6 +2952,7 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix1) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("3", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 4-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("efg"), res->body);
|
||||
}
|
||||
|
||||
@@ -2937,6 +2962,7 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix2) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("7", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 0-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("abcdefg"), res->body);
|
||||
}
|
||||
|
||||
@@ -2953,6 +2979,7 @@ TEST_F(ServerTest, GetRangeWithMaxLongLength) {
|
||||
cli_.Get("/with-range", {{"Range", "bytes=0-9223372036854775807"}});
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("7", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("bytes 0-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(std::string("abcdefg"), res->body);
|
||||
}
|
||||
@@ -3014,6 +3041,7 @@ TEST_F(ServerTest, GetWithRange1) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("3", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 3-5/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("def"), res->body);
|
||||
}
|
||||
|
||||
@@ -3023,6 +3051,7 @@ TEST_F(ServerTest, GetWithRange2) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("6", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 1-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("bcdefg"), res->body);
|
||||
}
|
||||
|
||||
@@ -3032,6 +3061,7 @@ TEST_F(ServerTest, GetWithRange3) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("1", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 0-0/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("a"), res->body);
|
||||
}
|
||||
|
||||
@@ -3041,6 +3071,7 @@ TEST_F(ServerTest, GetWithRange4) {
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 5-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("fg"), res->body);
|
||||
}
|
||||
|
||||
@@ -6511,18 +6542,103 @@ TEST(SocketStream, is_writable_INET) {
|
||||
#endif // #ifndef _WIN32
|
||||
|
||||
TEST(TaskQueueTest, IncreaseAtomicInteger) {
|
||||
static constexpr unsigned int number_of_task{1000000};
|
||||
static constexpr unsigned int number_of_tasks{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(
|
||||
for (unsigned int i = 0; i < number_of_tasks; ++i) {
|
||||
auto queued = task_queue->enqueue(
|
||||
[&count] { count.fetch_add(1, std::memory_order_relaxed); });
|
||||
EXPECT_TRUE(queued);
|
||||
}
|
||||
|
||||
EXPECT_NO_THROW(task_queue->shutdown());
|
||||
EXPECT_EQ(number_of_tasks, count.load());
|
||||
}
|
||||
|
||||
TEST(TaskQueueTest, IncreaseAtomicIntegerWithQueueLimit) {
|
||||
static constexpr unsigned int number_of_tasks{1000000};
|
||||
static constexpr unsigned int qlimit{2};
|
||||
unsigned int queued_count{0};
|
||||
std::atomic_uint count{0};
|
||||
std::unique_ptr<TaskQueue> task_queue{
|
||||
new ThreadPool{/*num_threads=*/1, qlimit}};
|
||||
|
||||
for (unsigned int i = 0; i < number_of_tasks; ++i) {
|
||||
if (task_queue->enqueue(
|
||||
[&count] { count.fetch_add(1, std::memory_order_relaxed); })) {
|
||||
queued_count++;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_NO_THROW(task_queue->shutdown());
|
||||
EXPECT_EQ(queued_count, count.load());
|
||||
EXPECT_TRUE(queued_count <= number_of_tasks);
|
||||
EXPECT_TRUE(queued_count >= qlimit);
|
||||
}
|
||||
|
||||
TEST(TaskQueueTest, MaxQueuedRequests) {
|
||||
static constexpr unsigned int qlimit{3};
|
||||
std::unique_ptr<TaskQueue> task_queue{new ThreadPool{1, qlimit}};
|
||||
std::condition_variable sem_cv;
|
||||
std::mutex sem_mtx;
|
||||
int credits = 0;
|
||||
bool queued;
|
||||
|
||||
/* Fill up the queue with tasks that will block until we give them credits to
|
||||
* complete. */
|
||||
for (unsigned int n = 0; n <= qlimit;) {
|
||||
queued = task_queue->enqueue([&sem_mtx, &sem_cv, &credits] {
|
||||
std::unique_lock<std::mutex> lock(sem_mtx);
|
||||
while (credits <= 0) {
|
||||
sem_cv.wait(lock);
|
||||
}
|
||||
/* Consume the credit and signal the test code if they are all gone. */
|
||||
if (--credits == 0) { sem_cv.notify_one(); }
|
||||
});
|
||||
|
||||
if (n < qlimit) {
|
||||
/* The first qlimit enqueues must succeed. */
|
||||
EXPECT_TRUE(queued);
|
||||
} else {
|
||||
/* The last one will succeed only when the worker thread
|
||||
* starts and dequeues the first blocking task. Although
|
||||
* not necessary for the correctness of this test, we sleep for
|
||||
* a short while to avoid busy waiting. */
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
if (queued) { n++; }
|
||||
}
|
||||
|
||||
/* Further enqueues must fail since the queue is full. */
|
||||
for (auto i = 0; i < 4; i++) {
|
||||
queued = task_queue->enqueue([] {});
|
||||
EXPECT_FALSE(queued);
|
||||
}
|
||||
|
||||
/* Give the credits to allow the previous tasks to complete. */
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(sem_mtx);
|
||||
credits += qlimit + 1;
|
||||
}
|
||||
sem_cv.notify_all();
|
||||
|
||||
/* Wait for all the credits to be consumed. */
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(sem_mtx);
|
||||
while (credits > 0) {
|
||||
sem_cv.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that we are able again to enqueue at least qlimit tasks. */
|
||||
for (unsigned int i = 0; i < qlimit; i++) {
|
||||
queued = task_queue->enqueue([] {});
|
||||
EXPECT_TRUE(queued);
|
||||
}
|
||||
|
||||
EXPECT_NO_THROW(task_queue->shutdown());
|
||||
EXPECT_EQ(number_of_task, count.load());
|
||||
}
|
||||
|
||||
TEST(RedirectTest, RedirectToUrlWithQueryParameters) {
|
||||
|
||||
Reference in New Issue
Block a user