Compare commits

...

8 Commits

Author SHA1 Message Date
yhirose
0654e5dab4 Changed CPPHTTPLIB_IDLE_INTERVAL_USECOND to 0 2020-05-23 08:44:03 -04:00
yhirose
62e036f253 Fixed #488 again 2020-05-22 18:24:01 -04:00
yhirose
f0adfb2e0c Fix #488 2020-05-22 12:18:07 -04:00
yhirose
139c816c16 Fixed the location of Client2 2020-05-19 21:02:58 -04:00
KTGH
9505a76491 Bringing Cmake back (#470)
* Revert "Removed CMakeLists.txt. (Fix #421)"

This reverts commit 8674555b88.

* Fail if cmake version too old

Previous behaviour is just a warning.

* Improve CMakeLists

Adds automatic dependency finding (if they were used).
Adds a way to require a specific version in the find_package(httplib) call.

You should link against the httplib::httplib IMPORTED target, which is
created automatically.

Add options to allow for strictly requiring OpenSSL/ZLIB

HTTPLIB_REQUIRE_OPENSSL & HTTPLIB_REQUIRE_ZLIB require the libs be found, or the build fails.

HTTPLIB_USE_OPENSSL_IF_AVAILABLE & HTTPLIB_USE_ZLIB_IF_AVAILABLE silently search for the libs.
If they aren't found, the build still continues, but shuts off support for those features.

* Add documentation to CMakeLists.txt

Has info on all the available options and what targets are produced.

Also put some things about installation on certain platforms.
2020-05-19 19:07:18 -04:00
yhirose
29fd136afd Code cleanup and format 2020-05-16 17:35:04 -04:00
yhirose
f5598237b2 Fixed many redirects problem on Proxy 2020-05-16 17:34:03 -04:00
Daniel Ottiger
01058659ab make write timeout configurable (like the read timeout already is) (#477)
In case we want to send a lot of data,
and the receiver is slower than the sender.

This will first fill up the receivers queues and after this
eventually also the senders queues,
until the socket is temporarily unable to accept more data to send.

select_write is done with an timeout of zero,
which makes the select call used always return immediately:
(see http://man7.org/linux/man-pages/man2/select.2.html)

This means that every marginal unavailability will make it return false
for is_writable and therefore httplib will immediately abort the transfer.

Therefore make this values configurable in the same way
as the read timeout already is.

Set the default write timeout to 5 seconds,
the same default value used for the read timeout.
2020-05-16 17:31:46 -04:00
5 changed files with 703 additions and 416 deletions

160
CMakeLists.txt Normal file
View File

@@ -0,0 +1,160 @@
#[[
Build options:
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
* HTTPLIB_REQUIRE_OPENSSL (default off)
* HTTPLIB_REQUIRE_ZLIB (default off)
After installation with Cmake, a find_package(httplib) is available.
This creates a httplib::httplib target (if found).
It can be linked like so:
target_link_libraries(your_exe httplib::httplib)
The following will build & install for later use.
Linux/macOS:
mkdir -p build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
sudo cmake --build . --target install
Windows:
mkdir build
cd build
cmake ..
runas /user:Administrator "cmake --build . --config Release --target install"
These three variables are available after you run find_package(httplib)
* HTTPLIB_HEADER_PATH - this is the full path to the installed header.
* HTTPLIB_IS_USING_OPENSSL - a bool for if OpenSSL support is enabled.
* HTTPLIB_IS_USING_ZLIB - a bool for if ZLIB support is enabled.
Want to use precompiled headers (Cmake feature since v3.16)?
It's as simple as doing the following (before linking):
target_precompile_headers(httplib::httplib INTERFACE "${HTTPLIB_HEADER_PATH}")
]]
cmake_minimum_required(VERSION 3.7.0 FATAL_ERROR)
project(httplib 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 "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)
# Allow for a build to casually enable OpenSSL/ZLIB support, but silenty continue if not found.
# Make these options so their automatic use can be specifically disabled (as needed)
option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable HTTPS support." ON)
option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable compression support." ON)
# TODO: implement the option of option to correctly split, building, and export with the split.py script.
# option(HTTPLIB_SPLIT "Uses a Python script to split the header into a header & source file." OFF)
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
find_package(Threads REQUIRED)
if(HTTPLIB_REQUIRE_OPENSSL)
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} REQUIRED)
elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE)
# Look quietly since it's optional are optional
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} QUIET)
endif()
if(HTTPLIB_REQUIRE_ZLIB)
find_package(ZLIB REQUIRED)
elseif(HTTPLIB_USE_ZLIB_IF_AVAILABLE)
find_package(ZLIB QUIET)
endif()
# Used for default, common dirs that the end-user can change (if needed)
# like CMAKE_INSTALL_INCLUDEDIR or CMAKE_INSTALL_DATADIR
include(GNUInstallDirs)
add_library(${PROJECT_NAME} INTERFACE)
# Lets you address the target with httplib::httplib
# Only useful if building in-tree, versus using it from an installation.
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
# Might be missing some, but this list is somewhat comprehensive
target_compile_features(${PROJECT_NAME} INTERFACE
cxx_std_11
cxx_nullptr
cxx_noexcept
cxx_lambdas
cxx_override
cxx_defaulted_functions
cxx_attribute_deprecated
cxx_auto_type
cxx_decltype
cxx_deleted_functions
cxx_range_for
cxx_sizeof_member
)
target_include_directories(${PROJECT_NAME} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_link_libraries(${PROJECT_NAME} INTERFACE
# Always require threads
Threads::Threads
# Only link zlib & openssl if they're found
$<$<BOOL:${ZLIB_FOUND}>:ZLIB::ZLIB>
$<$<BOOL:${OPENSSL_FOUND}>:OpenSSL::SSL OpenSSL::Crypto>
)
# Auto-define the optional support if those packages were found
target_compile_definitions(${PROJECT_NAME} INTERFACE
$<$<BOOL:${ZLIB_FOUND}>:CPPHTTPLIB_ZLIB_SUPPORT>
$<$<BOOL:${OPENSSL_FOUND}>:CPPHTTPLIB_OPENSSL_SUPPORT>
)
# Cmake's find_package search path is different based on the system
# See https://cmake.org/cmake/help/latest/command/find_package.html for the list
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_PREFIX}/cmake/${PROJECT_NAME}")
else()
# On Non-Windows, it should be /usr/lib/cmake/<name>/<name>Config.cmake
# NOTE: This may or may not work for macOS...
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
endif()
include(CMakePackageConfigHelpers)
# Configures the meta-file httplibConfig.cmake.in to replace variables with paths/values/etc.
configure_package_config_file("${PROJECT_NAME}Config.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
INSTALL_DESTINATION "${_TARGET_INSTALL_CMAKEDIR}"
# Passes the includedir install path
PATH_VARS CMAKE_INSTALL_FULL_INCLUDEDIR
# There aren't any components, so don't use the macro
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# 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
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(FILES httplib.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
)
# NOTE: This path changes depending on if it's on Windows or Linux
install(EXPORT httplibTargets
# Puts the targets into the httplib namespace
# So this makes httplib::httplib linkable after doing find_package(httplib)
NAMESPACE ${PROJECT_NAME}::
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
)

837
httplib.h

File diff suppressed because it is too large Load Diff

38
httplibConfig.cmake.in Normal file
View File

@@ -0,0 +1,38 @@
# Generates a macro to auto-configure everything
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
# We add find_dependency calls here to not make the end-user have to call them.
find_dependency(Threads REQUIRED)
if(@HTTPLIB_REQUIRE_OPENSSL@)
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ REQUIRED)
# Lets you check if these options were correctly enabled for your install
set(HTTPLIB_IS_USING_OPENSSL TRUE)
elseif(@HTTPLIB_USE_OPENSSL_IF_AVAILABLE@)
# Look quietly since it's optional
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ QUIET)
# Lets you check if these options were correctly enabled for your install
set(HTTPLIB_IS_USING_OPENSSL @OPENSSL_FOUND@)
else()
set(HTTPLIB_IS_USING_OPENSSL FALSE)
endif()
if(@HTTPLIB_REQUIRE_ZLIB@)
find_dependency(ZLIB REQUIRED)
# Lets you check if these options were correctly enabled for your install
set(HTTPLIB_IS_USING_ZLIB TRUE)
elseif(@HTTPLIB_USE_ZLIB_IF_AVAILABLE@)
# Look quietly since it's optional
find_dependency(ZLIB QUIET)
# Lets you check if these options were correctly enabled for your install
set(HTTPLIB_IS_USING_ZLIB @ZLIB_FOUND@)
else()
set(HTTPLIB_IS_USING_ZLIB FALSE)
endif()
# Lets the end-user find the header path if needed
# This is helpful if you're using Cmake's pre-compiled header feature
set_and_check(HTTPLIB_HEADER_PATH "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@/httplib.h")
# Brings in the target library
include("${CMAKE_CURRENT_LIST_DIR}/httplibTargets.cmake")

View File

@@ -334,7 +334,7 @@ TEST(RangeTest, FromHTTPBin) {
httplib::Headers headers;
auto res = cli.Get("/range/32", headers);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef");
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
}
@@ -342,7 +342,7 @@ TEST(RangeTest, FromHTTPBin) {
httplib::Headers headers = {httplib::make_range_header({{1, -1}})};
auto res = cli.Get("/range/32", headers);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "bcdefghijklmnopqrstuvwxyzabcdef");
EXPECT_EQ("bcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(206, res->status);
}
@@ -350,7 +350,7 @@ TEST(RangeTest, FromHTTPBin) {
httplib::Headers headers = {httplib::make_range_header({{1, 10}})};
auto res = cli.Get("/range/32", headers);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "bcdefghijk");
EXPECT_EQ("bcdefghijk", res->body);
EXPECT_EQ(206, res->status);
}
@@ -358,7 +358,7 @@ TEST(RangeTest, FromHTTPBin) {
httplib::Headers headers = {httplib::make_range_header({{0, 31}})};
auto res = cli.Get("/range/32", headers);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef");
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
}
@@ -366,7 +366,7 @@ TEST(RangeTest, FromHTTPBin) {
httplib::Headers headers = {httplib::make_range_header({{0, -1}})};
auto res = cli.Get("/range/32", headers);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef");
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
}
@@ -440,7 +440,7 @@ TEST(CancelTest, NoCancel) {
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef");
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
}
@@ -501,8 +501,8 @@ TEST(BaseAuthTest, FromHTTPWatch) {
cli.Get("/basic-auth/hello/world",
{httplib::make_basic_authentication_header("hello", "world")});
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body,
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(200, res->status);
}
@@ -510,8 +510,8 @@ TEST(BaseAuthTest, FromHTTPWatch) {
cli.set_basic_auth("hello", "world");
auto res = cli.Get("/basic-auth/hello/world");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body,
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(200, res->status);
}
@@ -554,8 +554,8 @@ TEST(DigestAuthTest, FromHTTPWatch) {
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body,
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(200, res->status);
}
@@ -689,7 +689,7 @@ TEST(RedirectToDifferentPort, Redirect) {
auto res = cli.Get("/1");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ(res->body, "Hello World!");
EXPECT_EQ("Hello World!", res->body);
svr8080.stop();
svr8081.stop();
@@ -718,7 +718,7 @@ TEST(Server, BindDualStack) {
auto res = cli.Get("/1");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ(res->body, "Hello World!");
EXPECT_EQ("Hello World!", res->body);
}
{
Client cli("::1", PORT);
@@ -726,7 +726,7 @@ TEST(Server, BindDualStack) {
auto res = cli.Get("/1");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ(res->body, "Hello World!");
EXPECT_EQ("Hello World!", res->body);
}
svr.stop();
thread.join();
@@ -807,6 +807,11 @@ protected:
std::this_thread::sleep_for(std::chrono::seconds(2));
res.set_content("slow", "text/plain");
})
.Post("/slowpost",
[&](const Request & /*req*/, Response &res) {
std::this_thread::sleep_for(std::chrono::seconds(2));
res.set_content("slow", "text/plain");
})
.Get("/remote_addr",
[&](const Request &req, Response &res) {
auto remote_addr = req.headers.find("REMOTE_ADDR")->second;
@@ -1885,6 +1890,33 @@ TEST_F(ServerTest, SlowRequest) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
TEST_F(ServerTest, SlowPost) {
char buffer[64 * 1024];
memset(buffer, 0x42, sizeof(buffer));
auto res = cli_.Post(
"/slowpost", 64 * 1024 * 1024,
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
sink.write(buffer, sizeof(buffer));
return true;
},
"text/plain");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
cli_.set_write_timeout(0, 0);
res = cli_.Post(
"/slowpost", 64 * 1024 * 1024,
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
sink.write(buffer, sizeof(buffer));
return true;
},
"text/plain");
ASSERT_FALSE(res != nullptr);
}
TEST_F(ServerTest, Put) {
auto res = cli_.Put("/put", "PUT", "text/plain");
ASSERT_TRUE(res != nullptr);
@@ -2253,7 +2285,7 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
if (client_sock == INVALID_SOCKET) { return false; }
return detail::process_and_close_socket(
true, client_sock, 1, read_timeout_sec, 0,
true, client_sock, 1, read_timeout_sec, 0, 0, 0,
[&](Stream &strm, bool /*last_connection*/, bool &
/*connection_close*/) -> bool {
if (req.size() !=
@@ -2438,7 +2470,7 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
svr.Get("/events", [](const Request & /*req*/, Response &res) {
res.set_header("Content-Type", "text/event-stream");
res.set_header("Cache-Control", "no-cache");
res.set_chunked_content_provider([](size_t offset, const DataSink &sink) {
res.set_chunked_content_provider([](size_t offset, DataSink &sink) {
char buffer[27];
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
sink.write(buffer, size);

View File

@@ -185,15 +185,17 @@ void DigestAuthTestFromHTTPWatch(Client& cli) {
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(400, res->status);
EXPECT_EQ(401, res->status);
}
cli.set_digest_auth("bad", "world");
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(400, res->status);
}
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
// out. Plese see https://httpbin.org/digest-auth/auth/hello/world
// cli.set_digest_auth("bad", "world");
// for (auto path : paths) {
// auto res = cli.Get(path.c_str());
// ASSERT_TRUE(res != nullptr);
// EXPECT_EQ(401, res->status);
// }
}
}
@@ -266,7 +268,7 @@ void KeepAliveTest(Client& cli, bool basic) {
{
int count = paths.size();
int count = static_cast<int>(paths.size());
while (count--) {
auto &res = responses[i++];
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res.body);