mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-08 16:04:46 +07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e6ded1f36 | ||
|
|
d663588491 | ||
|
|
439caf5b79 | ||
|
|
c4ba43ca6f | ||
|
|
20cba2ecd9 | ||
|
|
0ff2e16d69 | ||
|
|
51607ec752 | ||
|
|
7992b14896 | ||
|
|
7e420aeed3 | ||
|
|
227d2c2050 | ||
|
|
93e53c91f7 | ||
|
|
58cffd3223 | ||
|
|
8f32271e8c | ||
|
|
c8c1c3d376 | ||
|
|
9f512acb42 | ||
|
|
c0b461a3b7 | ||
|
|
74fe5a5029 | ||
|
|
9d0a9d4e23 | ||
|
|
5758769ad3 | ||
|
|
e7eadc3605 | ||
|
|
07c6e58951 | ||
|
|
87994811a1 | ||
|
|
42feb7e8be | ||
|
|
26196b70af | ||
|
|
93a51979c4 | ||
|
|
ad7edc7b27 | ||
|
|
27cd4e6ffe | ||
|
|
cae5a8be1c | ||
|
|
8e10d4e8e7 | ||
|
|
b57f79f438 | ||
|
|
a9cf097951 | ||
|
|
5c3624e1af | ||
|
|
cba9ef8c0b | ||
|
|
4f8407a3a7 | ||
|
|
656e936f49 | ||
|
|
d92c314466 | ||
|
|
b747fb111d | ||
|
|
7e0a0b2d0c | ||
|
|
362d064afa | ||
|
|
1bd88de2e5 | ||
|
|
0b541ffebc | ||
|
|
106be19c3e |
+13
-14
@@ -8,6 +8,7 @@
|
|||||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||||
* HTTPLIB_COMPILE (default off)
|
* HTTPLIB_COMPILE (default off)
|
||||||
|
* HTTPLIB_TEST (default off)
|
||||||
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
|
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
|
||||||
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
|
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a c
|
|||||||
if(HTTPLIB_COMPILE)
|
if(HTTPLIB_COMPILE)
|
||||||
set(HTTPLIB_IS_COMPILED TRUE)
|
set(HTTPLIB_IS_COMPILED TRUE)
|
||||||
endif()
|
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_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)
|
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
|
||||||
# Defaults to static library
|
# Defaults to static library
|
||||||
@@ -176,7 +177,7 @@ if(HTTPLIB_COMPILE)
|
|||||||
set_target_properties(${PROJECT_NAME}
|
set_target_properties(${PROJECT_NAME}
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
VERSION ${${PROJECT_NAME}_VERSION}
|
VERSION ${${PROJECT_NAME}_VERSION}
|
||||||
SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}
|
SOVERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}"
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
# This is for header-only.
|
# This is for header-only.
|
||||||
@@ -193,7 +194,7 @@ target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
|||||||
cxx_std_11
|
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}>
|
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
||||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||||
)
|
)
|
||||||
@@ -221,15 +222,8 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
|||||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
|
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
|
||||||
)
|
)
|
||||||
|
|
||||||
# Cmake's find_package search path is different based on the system
|
# CMake configuration files installation directory
|
||||||
# See https://cmake.org/cmake/help/latest/command/find_package.html for the list
|
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
|
||||||
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()
|
|
||||||
|
|
||||||
include(CMakePackageConfigHelpers)
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
@@ -247,13 +241,13 @@ if(HTTPLIB_COMPILE)
|
|||||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||||
# Example: if you find_package(httplib 0.5.4)
|
# Example: if you find_package(httplib 0.5.4)
|
||||||
# then anything >= 0.5 and <= 1.0 is accepted
|
# then anything >= 0.5 and <= 1.0 is accepted
|
||||||
COMPATIBILITY SameMajorVersion
|
COMPATIBILITY SameMinorVersion
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||||
# Example: if you find_package(httplib 0.5.4)
|
# Example: if you find_package(httplib 0.5.4)
|
||||||
# then anything >= 0.5 and <= 1.0 is accepted
|
# then anything >= 0.5 and <= 1.0 is accepted
|
||||||
COMPATIBILITY SameMajorVersion
|
COMPATIBILITY SameMinorVersion
|
||||||
# Tells Cmake that it's a header-only lib
|
# Tells Cmake that it's a header-only lib
|
||||||
# Mildly useful for end-users :)
|
# Mildly useful for end-users :)
|
||||||
ARCH_INDEPENDENT
|
ARCH_INDEPENDENT
|
||||||
@@ -285,3 +279,8 @@ install(EXPORT httplibTargets
|
|||||||
NAMESPACE ${PROJECT_NAME}::
|
NAMESPACE ${PROJECT_NAME}::
|
||||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(HTTPLIB_TEST)
|
||||||
|
include(CTest)
|
||||||
|
add_subdirectory(test)
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ A C++11 single-file header-only cross platform HTTP/HTTPS library.
|
|||||||
|
|
||||||
It's extremely easy to setup. Just include the **httplib.h** file in your code!
|
It's extremely easy to setup. Just include the **httplib.h** file in your code!
|
||||||
|
|
||||||
NOTE: This is a multi-threaded 'blocking' HTTP library. If you are looking for a 'non-blocking' library, this is not the one that you want.
|
NOTE: This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want.
|
||||||
|
|
||||||
Simple examples
|
Simple examples
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
#### Server
|
#### Server (Multi-threaded)
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
@@ -65,6 +65,7 @@ httplib::SSLServer svr("./cert.pem", "./key.pem");
|
|||||||
// Client
|
// Client
|
||||||
httplib::Client cli("https://localhost:1234"); // scheme + host
|
httplib::Client cli("https://localhost:1234"); // scheme + host
|
||||||
httplib::SSLClient cli("localhost:1234"); // host
|
httplib::SSLClient cli("localhost:1234"); // host
|
||||||
|
httplib::SSLClient cli("localhost", 1234); // host, port
|
||||||
|
|
||||||
// Use your CA bundle
|
// Use your CA bundle
|
||||||
cli.set_ca_cert_path("./ca-bundle.crt");
|
cli.set_ca_cert_path("./ca-bundle.crt");
|
||||||
@@ -492,6 +493,10 @@ auto res = cli.Get("/hi", headers);
|
|||||||
```
|
```
|
||||||
or
|
or
|
||||||
```c++
|
```c++
|
||||||
|
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```c++
|
||||||
cli.set_default_headers({
|
cli.set_default_headers({
|
||||||
{ "Accept-Encoding", "gzip, deflate" }
|
{ "Accept-Encoding", "gzip, deflate" }
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ public:
|
|||||||
unique_lock<mutex> lk(m_);
|
unique_lock<mutex> lk(m_);
|
||||||
int id = id_;
|
int id = id_;
|
||||||
cv_.wait(lk, [&] { return cid_ == id; });
|
cv_.wait(lk, [&] { return cid_ == id; });
|
||||||
if (sink->is_writable()) { sink->write(message_.data(), message_.size()); }
|
sink->write(message_.data(), message_.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_event(const string &message) {
|
void send_event(const string &message) {
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
#/usr/bin/env bash
|
#/usr/bin/env bash
|
||||||
for i in {1..10000}
|
for i in {1..1000000}
|
||||||
do
|
do
|
||||||
echo "#### $i ####"
|
echo "#### $i ####"
|
||||||
curl -X POST -F image_file=@$1 http://localhost:1234/post > /dev/null
|
curl -X POST -F image_file=@$1 http://localhost:1234/post > /dev/null
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ if get_option('cpp-httplib_compile')
|
|||||||
dependencies: deps,
|
dependencies: deps,
|
||||||
cpp_args: args,
|
cpp_args: args,
|
||||||
version: version,
|
version: version,
|
||||||
|
soversion: version.split('.')[0] + '.' + version.split('.')[1],
|
||||||
install: true
|
install: true
|
||||||
)
|
)
|
||||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1])
|
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1])
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
cmake_policy(SET CMP0135 NEW)
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
include(GoogleTest)
|
||||||
|
|
||||||
|
set(BUILD_GMOCK OFF)
|
||||||
|
set(INSTALL_GTEST OFF)
|
||||||
|
set(gtest_force_shared_crt ON)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
gtest
|
||||||
|
URL https://github.com/google/googletest/archive/main.tar.gz
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(gtest)
|
||||||
|
|
||||||
|
add_executable(httplib-test test.cc)
|
||||||
|
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
|
||||||
|
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main)
|
||||||
|
gtest_discover_tests(httplib-test)
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www www
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www2 www2
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www3 www3
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/ca-bundle.crt ca-bundle.crt
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/image.jpg image.jpg
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
|
||||||
|
if(HTTPLIB_IS_USING_OPENSSL)
|
||||||
|
find_program(OPENSSL_COMMAND
|
||||||
|
NAMES openssl
|
||||||
|
PATHS ${OPENSSL_INCLUDE_DIR}/../bin
|
||||||
|
REQUIRED
|
||||||
|
)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} genrsa 2048
|
||||||
|
OUTPUT_FILE key.pem
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem
|
||||||
|
COMMAND ${OPENSSL_COMMAND} x509 -days 3650 -req -signkey key.pem
|
||||||
|
OUTPUT_FILE cert.pem
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} req -x509 -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} genrsa 2048
|
||||||
|
OUTPUT_FILE rootCA.key.pem
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} req -x509 -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
|
||||||
|
OUTPUT_FILE rootCA.cert.pem
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} genrsa 2048
|
||||||
|
OUTPUT_FILE client.key.pem
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key client.key.pem
|
||||||
|
COMMAND ${OPENSSL_COMMAND} x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial
|
||||||
|
OUTPUT_FILE client.cert.pem
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} genrsa -passout pass:test123! 2048
|
||||||
|
OUTPUT_FILE key_encrypted.pem
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key_encrypted.pem
|
||||||
|
COMMAND ${OPENSSL_COMMAND} x509 -days 3650 -req -signkey key_encrypted.pem
|
||||||
|
OUTPUT_FILE cert_encrypted.pem
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
endif()
|
||||||
Binary file not shown.
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()); }
|
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_readable() const override { return true; }
|
||||||
|
|
||||||
bool is_writable() const override { return true; }
|
bool is_writable() const override { return true; }
|
||||||
@@ -33,6 +31,11 @@ public:
|
|||||||
port = 8080;
|
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; }
|
socket_t socket() const override { return 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
+819
-17
@@ -1,10 +1,12 @@
|
|||||||
#include <httplib.h>
|
#include <httplib.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <future>
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@@ -594,7 +596,7 @@ TEST(ConnectionErrorTest, InvalidHostCheckResultErrorToString) {
|
|||||||
ASSERT_TRUE(!res);
|
ASSERT_TRUE(!res);
|
||||||
stringstream s;
|
stringstream s;
|
||||||
s << "error code: " << res.error();
|
s << "error code: " << res.error();
|
||||||
EXPECT_EQ("error code: Connection (2)", s.str());
|
EXPECT_EQ("error code: Could not establish connection (2)", s.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConnectionErrorTest, InvalidPort) {
|
TEST(ConnectionErrorTest, InvalidPort) {
|
||||||
@@ -942,6 +944,44 @@ TEST(UrlWithSpace, Redirect_Online) {
|
|||||||
|
|
||||||
#endif
|
#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) {
|
TEST(RedirectToDifferentPort, Redirect) {
|
||||||
Server svr1;
|
Server svr1;
|
||||||
svr1.Get("/1", [&](const Request & /*req*/, Response &res) {
|
svr1.Get("/1", [&](const Request & /*req*/, Response &res) {
|
||||||
@@ -1324,7 +1364,12 @@ TEST(NoContentTest, ContentLength) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RoutingHandlerTest, PreRoutingHandler) {
|
TEST(RoutingHandlerTest, PreRoutingHandler) {
|
||||||
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||||
|
ASSERT_TRUE(svr.is_valid());
|
||||||
|
#else
|
||||||
Server svr;
|
Server svr;
|
||||||
|
#endif
|
||||||
|
|
||||||
svr.set_pre_routing_handler([](const Request &req, Response &res) {
|
svr.set_pre_routing_handler([](const Request &req, Response &res) {
|
||||||
if (req.path == "/routing_handler") {
|
if (req.path == "/routing_handler") {
|
||||||
@@ -1355,7 +1400,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
|
|||||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
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);
|
Client cli(HOST, PORT);
|
||||||
|
#endif
|
||||||
|
|
||||||
auto res = cli.Get("/routing_handler");
|
auto res = cli.Get("/routing_handler");
|
||||||
ASSERT_TRUE(res);
|
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);
|
Client cli(HOST, PORT);
|
||||||
|
#endif
|
||||||
|
|
||||||
auto res = cli.Get("/hi");
|
auto res = cli.Get("/hi");
|
||||||
ASSERT_TRUE(res);
|
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);
|
Client cli(HOST, PORT);
|
||||||
|
#endif
|
||||||
|
|
||||||
auto res = cli.Get("/aaa");
|
auto res = cli.Get("/aaa");
|
||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
@@ -1521,6 +1581,17 @@ protected:
|
|||||||
std::stoi(req.get_header_value("REMOTE_PORT")));
|
std::stoi(req.get_header_value("REMOTE_PORT")));
|
||||||
res.set_content(remote_addr.c_str(), "text/plain");
|
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%",
|
.Get("/endwith%",
|
||||||
[&](const Request & /*req*/, Response &res) {
|
[&](const Request & /*req*/, Response &res) {
|
||||||
res.set_content("Hello World!", "text/plain");
|
res.set_content("Hello World!", "text/plain");
|
||||||
@@ -1579,7 +1650,6 @@ protected:
|
|||||||
[&](const Request & /*req*/, Response &res) {
|
[&](const Request & /*req*/, Response &res) {
|
||||||
res.set_chunked_content_provider(
|
res.set_chunked_content_provider(
|
||||||
"text/plain", [](size_t /*offset*/, DataSink &sink) {
|
"text/plain", [](size_t /*offset*/, DataSink &sink) {
|
||||||
EXPECT_TRUE(sink.is_writable());
|
|
||||||
sink.os << "123";
|
sink.os << "123";
|
||||||
sink.os << "456";
|
sink.os << "456";
|
||||||
sink.os << "789";
|
sink.os << "789";
|
||||||
@@ -1593,7 +1663,6 @@ protected:
|
|||||||
res.set_chunked_content_provider(
|
res.set_chunked_content_provider(
|
||||||
"text/plain",
|
"text/plain",
|
||||||
[i](size_t /*offset*/, DataSink &sink) {
|
[i](size_t /*offset*/, DataSink &sink) {
|
||||||
EXPECT_TRUE(sink.is_writable());
|
|
||||||
switch (*i) {
|
switch (*i) {
|
||||||
case 0: sink.os << "123"; break;
|
case 0: sink.os << "123"; break;
|
||||||
case 1: sink.os << "456"; break;
|
case 1: sink.os << "456"; break;
|
||||||
@@ -1623,7 +1692,6 @@ protected:
|
|||||||
res.set_content_provider(
|
res.set_content_provider(
|
||||||
data->size(), "text/plain",
|
data->size(), "text/plain",
|
||||||
[data](size_t offset, size_t length, DataSink &sink) {
|
[data](size_t offset, size_t length, DataSink &sink) {
|
||||||
EXPECT_TRUE(sink.is_writable());
|
|
||||||
size_t DATA_CHUNK_SIZE = 4;
|
size_t DATA_CHUNK_SIZE = 4;
|
||||||
const auto &d = *data;
|
const auto &d = *data;
|
||||||
auto out_len =
|
auto out_len =
|
||||||
@@ -1643,7 +1711,6 @@ protected:
|
|||||||
res.set_content_provider(
|
res.set_content_provider(
|
||||||
size_t(-1), "text/plain",
|
size_t(-1), "text/plain",
|
||||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||||
EXPECT_TRUE(sink.is_writable());
|
|
||||||
sink.os << "data_chunk";
|
sink.os << "data_chunk";
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -1714,6 +1781,22 @@ protected:
|
|||||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||||
res.set_content("empty-no-content-type", "text/plain");
|
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",
|
.Post("/post-large",
|
||||||
[&](const Request &req, Response &res) {
|
[&](const Request &req, Response &res) {
|
||||||
EXPECT_EQ(req.body, LARGE_DATA);
|
EXPECT_EQ(req.body, LARGE_DATA);
|
||||||
@@ -2124,6 +2207,21 @@ TEST_F(ServerTest, PostEmptyContentWithNoContentType) {
|
|||||||
ASSERT_EQ("empty-no-content-type", res->body);
|
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) {
|
TEST_F(ServerTest, PostLarge) {
|
||||||
auto res = cli_.Post("/post-large", LARGE_DATA, "text/plain");
|
auto res = cli_.Post("/post-large", LARGE_DATA, "text/plain");
|
||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
@@ -2778,6 +2876,15 @@ TEST_F(ServerTest, GetMethodRemoteAddr) {
|
|||||||
EXPECT_TRUE(res->body == "::1" || res->body == "127.0.0.1");
|
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) {
|
TEST_F(ServerTest, HTTPResponseSplitting) {
|
||||||
auto res = cli_.Get("/http_response_splitting");
|
auto res = cli_.Get("/http_response_splitting");
|
||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
@@ -2840,7 +2947,6 @@ TEST_F(ServerTest, PutWithContentProvider) {
|
|||||||
auto res = cli_.Put(
|
auto res = cli_.Put(
|
||||||
"/put", 3,
|
"/put", 3,
|
||||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||||
EXPECT_TRUE(sink.is_writable());
|
|
||||||
sink.os << "PUT";
|
sink.os << "PUT";
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -2867,7 +2973,6 @@ TEST_F(ServerTest, PutWithContentProviderWithoutLength) {
|
|||||||
auto res = cli_.Put(
|
auto res = cli_.Put(
|
||||||
"/put",
|
"/put",
|
||||||
[](size_t /*offset*/, DataSink &sink) {
|
[](size_t /*offset*/, DataSink &sink) {
|
||||||
EXPECT_TRUE(sink.is_writable());
|
|
||||||
sink.os << "PUT";
|
sink.os << "PUT";
|
||||||
sink.done();
|
sink.done();
|
||||||
return true;
|
return true;
|
||||||
@@ -2894,7 +2999,6 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
|||||||
auto res = cli_.Put(
|
auto res = cli_.Put(
|
||||||
"/put", 3,
|
"/put", 3,
|
||||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||||
EXPECT_TRUE(sink.is_writable());
|
|
||||||
sink.os << "PUT";
|
sink.os << "PUT";
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -2923,7 +3027,6 @@ TEST_F(ServerTest, PutWithContentProviderWithoutLengthWithGzip) {
|
|||||||
auto res = cli_.Put(
|
auto res = cli_.Put(
|
||||||
"/put",
|
"/put",
|
||||||
[](size_t /*offset*/, DataSink &sink) {
|
[](size_t /*offset*/, DataSink &sink) {
|
||||||
EXPECT_TRUE(sink.is_writable());
|
|
||||||
sink.os << "PUT";
|
sink.os << "PUT";
|
||||||
sink.done();
|
sink.done();
|
||||||
return true;
|
return true;
|
||||||
@@ -3807,9 +3910,12 @@ TEST(MountTest, Unmount) {
|
|||||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
svr.Get("/hi", [&](const Request & /*req*/, Response & /*res*/) {
|
svr.Get("/exception", [&](const Request & /*req*/, Response & /*res*/) {
|
||||||
throw std::runtime_error("exception...");
|
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); });
|
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||||
@@ -3822,11 +3928,21 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
|
|||||||
|
|
||||||
Client cli("localhost", PORT);
|
Client cli("localhost", PORT);
|
||||||
|
|
||||||
auto res = cli.Get("/hi");
|
{
|
||||||
ASSERT_TRUE(res);
|
auto res = cli.Get("/exception");
|
||||||
EXPECT_EQ(500, res->status);
|
ASSERT_TRUE(res);
|
||||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
EXPECT_EQ(500, res->status);
|
||||||
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
|
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();
|
svr.stop();
|
||||||
listen_thread.join();
|
listen_thread.join();
|
||||||
@@ -4180,7 +4296,8 @@ protected:
|
|||||||
res.set_content("Hello World!", "text/plain");
|
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()) {
|
while (!svr_.is_running()) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
@@ -4750,6 +4867,43 @@ TEST(SendAPI, SimpleInterface_Online) {
|
|||||||
EXPECT_EQ(301, res->status);
|
EXPECT_EQ(301, res->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ClientImplMethods, GetSocketTest) {
|
||||||
|
httplib::Server svr;
|
||||||
|
svr.Get("/", [&](const httplib::Request & /*req*/, httplib::Response &res) {
|
||||||
|
res.status = 200;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto thread = std::thread([&]() { svr.listen("127.0.0.1", 3333); });
|
||||||
|
|
||||||
|
while (!svr.is_running()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
httplib::Client cli("http://127.0.0.1:3333");
|
||||||
|
cli.set_keep_alive(true);
|
||||||
|
|
||||||
|
// Use the behavior of cpp-httplib of opening the connection
|
||||||
|
// only when the first request happens. If that changes,
|
||||||
|
// this test would be obsolete.
|
||||||
|
|
||||||
|
EXPECT_EQ(cli.socket(), INVALID_SOCKET);
|
||||||
|
|
||||||
|
// This also implicitly tests the server. But other tests would fail much
|
||||||
|
// earlier than this one to be considered.
|
||||||
|
|
||||||
|
auto res = cli.Get("/");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
|
||||||
|
EXPECT_EQ(200, res->status);
|
||||||
|
ASSERT_TRUE(cli.socket() != INVALID_SOCKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.stop();
|
||||||
|
thread.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
// Disabled due to out-of-memory problem on GitHub Actions
|
// Disabled due to out-of-memory problem on GitHub Actions
|
||||||
#ifdef _WIN64
|
#ifdef _WIN64
|
||||||
TEST(ServerLargeContentTest, DISABLED_SendLargeContent) {
|
TEST(ServerLargeContentTest, DISABLED_SendLargeContent) {
|
||||||
@@ -4983,6 +5137,247 @@ TEST(MultipartFormDataTest, LargeData) {
|
|||||||
t.join();
|
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, BadHeader) {
|
||||||
|
Server svr;
|
||||||
|
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&] { svr.listen(HOST, PORT); });
|
||||||
|
while (!svr.is_running()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string body =
|
||||||
|
"This is the preamble. It is to be ignored, though it\r\n"
|
||||||
|
"is a handy place for composition agents to include an\r\n"
|
||||||
|
"explanatory note to non-MIME conformant readers.\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"--simple boundary\r\n"
|
||||||
|
"Content-Disposition: form-data; name=\"field1\"\r\n"
|
||||||
|
": BAD...\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"value1\r\n"
|
||||||
|
"--simple boundary\r\n"
|
||||||
|
"Content-Disposition: form-data; name=\"field2\"; "
|
||||||
|
"filename=\"example.txt\"\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"value2\r\n"
|
||||||
|
"--simple boundary--\r\n"
|
||||||
|
"This is the epilogue. It is also to be ignored.\r\n";
|
||||||
|
|
||||||
|
std::string content_type =
|
||||||
|
R"(multipart/form-data; boundary="simple boundary")";
|
||||||
|
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
auto res = cli.Post("/post", body, content_type.c_str());
|
||||||
|
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(400, res->status);
|
||||||
|
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
|
||||||
TEST(MultipartFormDataTest, WithPreamble) {
|
TEST(MultipartFormDataTest, WithPreamble) {
|
||||||
Server svr;
|
Server svr;
|
||||||
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
||||||
@@ -5025,5 +5420,412 @@ TEST(MultipartFormDataTest, WithPreamble) {
|
|||||||
t.join();
|
t.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string data(1024 * 1024 * 2, '.');
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << data;
|
||||||
|
|
||||||
|
Client cli("https://localhost:8080");
|
||||||
|
cli.enable_server_certificate_verification(false);
|
||||||
|
|
||||||
|
MultipartFormDataItems items{
|
||||||
|
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
|
||||||
|
{"hello", "world", "", ""},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto res = cli.Post("/post_customboundary", {}, items, "abc-abc");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
ASSERT_EQ(200, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MultipartFormDataTest, PostInvalidBoundaryChars) {
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
|
||||||
|
std::string data(1024 * 1024 * 2, '&');
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << data;
|
||||||
|
|
||||||
|
Client cli("https://localhost:8080");
|
||||||
|
|
||||||
|
MultipartFormDataItems items{
|
||||||
|
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
|
||||||
|
{"hello", "world", "", ""},
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||||
|
|
||||||
|
svr.Put("/put", [&](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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string data(1024 * 1024 * 2, '&');
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << data;
|
||||||
|
|
||||||
|
Client cli("https://localhost:8080");
|
||||||
|
cli.enable_server_certificate_verification(false);
|
||||||
|
|
||||||
|
MultipartFormDataItems items{
|
||||||
|
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
|
||||||
|
{"hello", "world", "", ""},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto res = cli.Put("/put", items);
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
ASSERT_EQ(200, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string data(1024 * 1024 * 2, '&');
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << data;
|
||||||
|
|
||||||
|
Client cli("https://localhost:8080");
|
||||||
|
cli.enable_server_certificate_verification(false);
|
||||||
|
|
||||||
|
MultipartFormDataItems items{
|
||||||
|
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
|
||||||
|
{"hello", "world", "", ""},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto res = cli.Put("/put_customboundary", {}, items, "abc-abc_");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
ASSERT_EQ(200, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MultipartFormDataTest, PutInvalidBoundaryChars) {
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
|
||||||
|
std::string data(1024 * 1024 * 2, '&');
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << data;
|
||||||
|
|
||||||
|
Client cli("https://localhost:8080");
|
||||||
|
cli.enable_server_certificate_verification(false);
|
||||||
|
|
||||||
|
MultipartFormDataItems items{
|
||||||
|
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
|
||||||
|
{"hello", "world", "", ""},
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
class UnixSocketTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void TearDown() override { std::remove(pathname_.c_str()); }
|
||||||
|
|
||||||
|
void client_GET(const std::string &addr) {
|
||||||
|
httplib::Client cli{addr};
|
||||||
|
cli.set_address_family(AF_UNIX);
|
||||||
|
ASSERT_TRUE(cli.is_valid());
|
||||||
|
|
||||||
|
const auto &result = cli.Get(pattern_);
|
||||||
|
ASSERT_TRUE(result) << "error: " << result.error();
|
||||||
|
|
||||||
|
const auto &resp = result.value();
|
||||||
|
EXPECT_EQ(resp.status, 200);
|
||||||
|
EXPECT_EQ(resp.body, content_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string pathname_{"./httplib-server.sock"};
|
||||||
|
const std::string pattern_{"/hi"};
|
||||||
|
const std::string content_{"Hello World!"};
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(UnixSocketTest, pathname) {
|
||||||
|
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(pathname_, 80));
|
||||||
|
}};
|
||||||
|
while (!svr.is_running()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(svr.is_running());
|
||||||
|
|
||||||
|
client_GET(pathname_);
|
||||||
|
|
||||||
|
svr.stop();
|
||||||
|
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};
|
||||||
|
|
||||||
|
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));
|
||||||
|
}};
|
||||||
|
while (!svr.is_running()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(svr.is_running());
|
||||||
|
|
||||||
|
client_GET(abstract_addr);
|
||||||
|
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST(SocketStream, is_writable_UNIX) {
|
||||||
|
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(fds[0], [&](Stream &s0) {
|
||||||
|
EXPECT_EQ(s0.socket(), fds[0]);
|
||||||
|
EXPECT_TRUE(s0.is_writable());
|
||||||
|
|
||||||
|
EXPECT_EQ(0, close(fds[1]));
|
||||||
|
EXPECT_FALSE(s0.is_writable());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
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_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
|
||||||
|
int disconnected_svr_sock = -1;
|
||||||
|
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, 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{[&] {
|
||||||
|
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, 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) {
|
||||||
|
EXPECT_EQ(ss.socket(), disconnected_svr_sock);
|
||||||
|
EXPECT_FALSE(ss.is_writable());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_EQ(0, close(disconnected_svr_sock));
|
||||||
|
}
|
||||||
|
#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