mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8612ac02d | |||
| 83ee6007da | |||
| 3eaa769a2d | |||
| b91540514d | |||
| ab563ff52c | |||
| 8cad160c0a | |||
| be7962f140 | |||
| 509b8570b0 | |||
| 630f3465a9 | |||
| 9af1a4a08f |
+96
-26
@@ -4,6 +4,9 @@
|
||||
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_REQUIRE_OPENSSL (default off)
|
||||
* HTTPLIB_REQUIRE_ZLIB (default off)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
After installation with Cmake, a find_package(httplib) is available.
|
||||
This creates a httplib::httplib target (if found).
|
||||
@@ -27,17 +30,24 @@
|
||||
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.
|
||||
* HTTPLIB_IS_COMPILED - a bool for if the library is header-only or compiled.
|
||||
|
||||
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}")
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
FindPython3 requires Cmake v3.12
|
||||
]]
|
||||
cmake_minimum_required(VERSION 3.7.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.12.0 FATAL_ERROR)
|
||||
project(httplib LANGUAGES CXX)
|
||||
|
||||
# Change as needed to set an OpenSSL minimum version.
|
||||
@@ -51,17 +61,23 @@ option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build.
|
||||
# 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)
|
||||
# Lets you compile the program as a regular library instead of header-only
|
||||
option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF)
|
||||
# Defaults to static library
|
||||
option(BUILD_SHARED_LIBS "Build the library as a shared library instead of static. Has no effect if using header-only." OFF)
|
||||
if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
|
||||
# Necessary for Windows if building shared libs
|
||||
# See https://stackoverflow.com/a/40743080
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
endif()
|
||||
|
||||
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
|
||||
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} REQUIRED)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL REQUIRED)
|
||||
elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE)
|
||||
# Look quietly since it's optional are optional
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} QUIET)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET)
|
||||
endif()
|
||||
if(HTTPLIB_REQUIRE_ZLIB)
|
||||
find_package(ZLIB REQUIRED)
|
||||
@@ -73,16 +89,53 @@ endif()
|
||||
# like CMAKE_INSTALL_INCLUDEDIR or CMAKE_INSTALL_DATADIR
|
||||
include(GNUInstallDirs)
|
||||
|
||||
add_library(${PROJECT_NAME} INTERFACE)
|
||||
if(HTTPLIB_COMPILE)
|
||||
# Put the split script into the build dir
|
||||
configure_file(split.py "${CMAKE_CURRENT_BINARY_DIR}/split.py"
|
||||
COPYONLY
|
||||
)
|
||||
# Needs to be in the same dir as the python script
|
||||
configure_file(httplib.h "${CMAKE_CURRENT_BINARY_DIR}/httplib.h"
|
||||
COPYONLY
|
||||
)
|
||||
|
||||
# Used outside of this if-else
|
||||
set(_INTERFACE_OR_PUBLIC PUBLIC)
|
||||
# Brings in the Python3_EXECUTABLE path we can use.
|
||||
find_package(Python3 REQUIRED)
|
||||
# Actually split the file
|
||||
# Keeps the output in the build dir to not pollute the main dir
|
||||
execute_process(COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/split.py"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
ERROR_VARIABLE _httplib_split_error
|
||||
)
|
||||
if(_httplib_split_error)
|
||||
message(FATAL_ERROR "Failed when trying to split Cpp-httplib with the Python script.\n${_httplib_split_error}")
|
||||
endif()
|
||||
|
||||
# split.py puts output in "out"
|
||||
set(_httplib_build_includedir "${CMAKE_CURRENT_BINARY_DIR}/out")
|
||||
# This will automatically be either static or shared based on the value of BUILD_SHARED_LIBS
|
||||
add_library(${PROJECT_NAME} "${_httplib_build_includedir}/httplib.cc")
|
||||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
|
||||
)
|
||||
else()
|
||||
# This is for header-only.
|
||||
set(_INTERFACE_OR_PUBLIC INTERFACE)
|
||||
add_library(${PROJECT_NAME} INTERFACE)
|
||||
set(_httplib_build_includedir "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
endif()
|
||||
# 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
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
cxx_std_11
|
||||
cxx_nullptr
|
||||
cxx_noexcept
|
||||
cxx_lambdas
|
||||
cxx_override
|
||||
cxx_defaulted_functions
|
||||
@@ -94,25 +147,42 @@ target_compile_features(${PROJECT_NAME} INTERFACE
|
||||
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>
|
||||
target_include_directories(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
|
||||
# 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>
|
||||
# Always require threads
|
||||
target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
Threads::Threads
|
||||
)
|
||||
|
||||
# We check for the target when using IF_AVAILABLE since it's possible we didn't find it.
|
||||
if(HTTPLIB_USE_OPENSSL_IF_AVAILABLE AND TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto OR HTTPLIB_REQUIRE_OPENSSL)
|
||||
target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
OpenSSL::SSL OpenSSL::Crypto
|
||||
)
|
||||
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
else()
|
||||
set(HTTPLIB_IS_USING_OPENSSL FALSE)
|
||||
endif()
|
||||
|
||||
# We check for the target when using IF_AVAILABLE since it's possible we didn't find it.
|
||||
if(HTTPLIB_USE_ZLIB_IF_AVAILABLE AND TARGET ZLIB::ZLIB OR HTTPLIB_REQUIRE_ZLIB)
|
||||
target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
ZLIB::ZLIB
|
||||
)
|
||||
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
CPPHTTPLIB_ZLIB_SUPPORT
|
||||
)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
else()
|
||||
set(HTTPLIB_IS_USING_ZLIB FALSE)
|
||||
endif()
|
||||
|
||||
# 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")
|
||||
@@ -144,7 +214,7 @@ install(TARGETS ${PROJECT_NAME}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
|
||||
install(FILES httplib.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
|
||||
@@ -203,6 +203,44 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
});
|
||||
```
|
||||
|
||||
### 'Expect: 100-continue' handler
|
||||
|
||||
As default, the server sends `100 Continue` response for `Expect: 100-continue` header.
|
||||
|
||||
```cpp
|
||||
// Send a '417 Expectation Failed' response.
|
||||
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||
return 417;
|
||||
});
|
||||
```
|
||||
|
||||
```cpp
|
||||
// Send a final status without reading the message body.
|
||||
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||
return res.status = 401;
|
||||
});
|
||||
```
|
||||
|
||||
### Keep-Alive connection
|
||||
|
||||
```cpp
|
||||
svr.set_keep_alive_max_count(2); // Default is 5
|
||||
```
|
||||
|
||||
### Timeout
|
||||
|
||||
```c++
|
||||
svr.set_read_timeout(5, 0); // 5 seconds
|
||||
svr.set_write_timeout(5, 0); // 5 seconds
|
||||
svr.set_idle_interval(0, 100000); // 100 milliseconds
|
||||
```
|
||||
|
||||
### Set maximum payload length for reading request body
|
||||
|
||||
```c++
|
||||
svr.set_payload_max_length(1024 * 1024 * 512); // 512MB
|
||||
```
|
||||
|
||||
### Server-Sent Events
|
||||
|
||||
Please check [here](https://github.com/yhirose/cpp-httplib/blob/master/example/sse.cc).
|
||||
@@ -240,24 +278,6 @@ svr.new_task_queue = [] {
|
||||
};
|
||||
```
|
||||
|
||||
### 'Expect: 100-continue' handler
|
||||
|
||||
As default, the server sends `100 Continue` response for `Expect: 100-continue` header.
|
||||
|
||||
```cpp
|
||||
// Send a '417 Expectation Failed' response.
|
||||
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||
return 417;
|
||||
});
|
||||
```
|
||||
|
||||
```cpp
|
||||
// Send a final status without reading the message body.
|
||||
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||
return res.status = 401;
|
||||
});
|
||||
```
|
||||
|
||||
Client Example
|
||||
--------------
|
||||
|
||||
@@ -360,11 +380,14 @@ res = cli.Options("*");
|
||||
res = cli.Options("/resource/foo");
|
||||
```
|
||||
|
||||
### Connection Timeout
|
||||
### Timeout
|
||||
|
||||
```c++
|
||||
cli.set_timeout_sec(5); // timeouts in 5 seconds
|
||||
cli.set_connection_timeout(0, 300000); // 300 milliseconds
|
||||
cli.set_read_timeout(5, 0); // 5 seconds
|
||||
cli.set_write_timeout(5, 0); // 5 seconds
|
||||
```
|
||||
|
||||
### Receive content with Content receiver
|
||||
|
||||
```cpp
|
||||
@@ -536,13 +559,21 @@ The server applies gzip compression to the following MIME type contents:
|
||||
* application/xml
|
||||
* application/xhtml+xml
|
||||
|
||||
### Compress content on client
|
||||
### Compress request body on client
|
||||
|
||||
```c++
|
||||
cli.set_compress(true);
|
||||
res = cli.Post("/resource/foo", "...", "text/plain");
|
||||
```
|
||||
|
||||
### Compress response body on client
|
||||
|
||||
```c++
|
||||
cli.set_decompress(false);
|
||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate"}});
|
||||
res->body; // Compressed data
|
||||
```
|
||||
|
||||
Split httplib.h into .h and .cc
|
||||
-------------------------------
|
||||
|
||||
|
||||
@@ -24,6 +24,14 @@
|
||||
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
|
||||
#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
|
||||
#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND
|
||||
#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5
|
||||
#endif
|
||||
@@ -45,8 +53,12 @@
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND
|
||||
#ifdef _WIN32
|
||||
#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000
|
||||
#else
|
||||
#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
|
||||
#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
|
||||
@@ -524,9 +536,10 @@ public:
|
||||
void set_expect_100_continue_handler(Expect100ContinueHandler handler);
|
||||
|
||||
void set_keep_alive_max_count(size_t count);
|
||||
void set_read_timeout(time_t sec, time_t usec);
|
||||
void set_write_timeout(time_t sec, time_t usec);
|
||||
void set_idle_interval(time_t sec, time_t usec);
|
||||
void set_read_timeout(time_t sec, time_t usec = 0);
|
||||
void set_write_timeout(time_t sec, time_t usec = 0);
|
||||
void set_idle_interval(time_t sec, time_t usec = 0);
|
||||
|
||||
void set_payload_max_length(size_t length);
|
||||
|
||||
bool bind_to_port(const char *host, int port, int socket_flags = 0);
|
||||
@@ -749,16 +762,14 @@ public:
|
||||
|
||||
void stop();
|
||||
|
||||
void set_timeout_sec(time_t timeout_sec);
|
||||
|
||||
void set_read_timeout(time_t sec, time_t usec);
|
||||
|
||||
void set_write_timeout(time_t sec, time_t usec);
|
||||
[[deprecated]] void set_timeout_sec(time_t timeout_sec);
|
||||
void set_connection_timeout(time_t sec, time_t usec = 0);
|
||||
void set_read_timeout(time_t sec, time_t usec = 0);
|
||||
void set_write_timeout(time_t sec, time_t usec = 0);
|
||||
|
||||
void set_keep_alive_max_count(size_t count);
|
||||
|
||||
void set_basic_auth(const char *username, const char *password);
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
void set_digest_auth(const char *username, const char *password);
|
||||
#endif
|
||||
@@ -767,12 +778,12 @@ public:
|
||||
|
||||
void set_compress(bool on);
|
||||
|
||||
void set_decompress(bool on);
|
||||
|
||||
void set_interface(const char *intf);
|
||||
|
||||
void set_proxy(const char *host, int port);
|
||||
|
||||
void set_proxy_basic_auth(const char *username, const char *password);
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
void set_proxy_digest_auth(const char *username, const char *password);
|
||||
#endif
|
||||
@@ -793,7 +804,8 @@ protected:
|
||||
std::string client_cert_path_;
|
||||
std::string client_key_path_;
|
||||
|
||||
time_t timeout_sec_ = 300;
|
||||
time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
|
||||
time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
|
||||
time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
|
||||
time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
|
||||
time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
|
||||
@@ -811,6 +823,7 @@ protected:
|
||||
bool follow_location_ = false;
|
||||
|
||||
bool compress_ = false;
|
||||
bool decompress_ = true;
|
||||
|
||||
std::string interface_;
|
||||
|
||||
@@ -829,7 +842,7 @@ protected:
|
||||
void copy_settings(const Client &rhs) {
|
||||
client_cert_path_ = rhs.client_cert_path_;
|
||||
client_key_path_ = rhs.client_key_path_;
|
||||
timeout_sec_ = rhs.timeout_sec_;
|
||||
connection_timeout_sec_ = rhs.connection_timeout_sec_;
|
||||
read_timeout_sec_ = rhs.read_timeout_sec_;
|
||||
read_timeout_usec_ = rhs.read_timeout_usec_;
|
||||
write_timeout_sec_ = rhs.write_timeout_sec_;
|
||||
@@ -843,6 +856,7 @@ protected:
|
||||
#endif
|
||||
follow_location_ = rhs.follow_location_;
|
||||
compress_ = rhs.compress_;
|
||||
decompress_ = rhs.decompress_;
|
||||
interface_ = rhs.interface_;
|
||||
proxy_host_ = rhs.proxy_host_;
|
||||
proxy_port_ = rhs.proxy_port_;
|
||||
@@ -1234,8 +1248,8 @@ public:
|
||||
|
||||
void stop() { cli_->stop(); }
|
||||
|
||||
Client2 &set_timeout_sec(time_t timeout_sec) {
|
||||
cli_->set_timeout_sec(timeout_sec);
|
||||
Client2 &set_connection_timeout(time_t sec, time_t usec) {
|
||||
cli_->set_connection_timeout(sec, usec);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -1271,6 +1285,11 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client2 &set_decompress(bool on) {
|
||||
cli_->set_decompress(on);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client2 &set_interface(const char *intf) {
|
||||
cli_->set_interface(intf);
|
||||
return *this;
|
||||
@@ -1615,9 +1634,6 @@ template <typename T> inline ssize_t handle_EINTR(T fn) {
|
||||
return res;
|
||||
}
|
||||
|
||||
#define HANDLE_EINTR(method, ...) \
|
||||
(handle_EINTR([&]() { return method(__VA_ARGS__); }))
|
||||
|
||||
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
struct pollfd pfd_read;
|
||||
@@ -1626,7 +1642,7 @@ inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
|
||||
#else
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
@@ -1636,8 +1652,9 @@ inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return HANDLE_EINTR(select, static_cast<int>(sock + 1), &fds, nullptr,
|
||||
nullptr, &tv);
|
||||
return handle_EINTR([&]() {
|
||||
return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1649,7 +1666,7 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
|
||||
#else
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
@@ -1659,8 +1676,9 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return HANDLE_EINTR(select, static_cast<int>(sock + 1), nullptr, &fds,
|
||||
nullptr, &tv);
|
||||
return handle_EINTR([&]() {
|
||||
return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1672,7 +1690,8 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
auto poll_res = HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
|
||||
|
||||
if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
@@ -1693,9 +1712,11 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
if (HANDLE_EINTR(select, static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse,
|
||||
&tv) > 0 &&
|
||||
(FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
|
||||
auto ret = handle_EINTR([&]() {
|
||||
return select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv);
|
||||
});
|
||||
|
||||
if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
return getsockopt(sock, SOL_SOCKET, SO_ERROR,
|
||||
@@ -1827,15 +1848,6 @@ inline int shutdown_socket(socket_t sock) {
|
||||
template <typename Fn>
|
||||
socket_t create_socket(const char *host, int port, Fn fn,
|
||||
int socket_flags = 0) {
|
||||
#ifdef _WIN32
|
||||
#define SO_SYNCHRONOUS_NONALERT 0x20
|
||||
#define SO_OPENTYPE 0x7008
|
||||
|
||||
int opt = SO_SYNCHRONOUS_NONALERT;
|
||||
setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&opt,
|
||||
sizeof(opt));
|
||||
#endif
|
||||
|
||||
// Get address info
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result;
|
||||
@@ -1977,7 +1989,7 @@ inline std::string if2ip(const std::string &ifn) {
|
||||
#endif
|
||||
|
||||
inline socket_t create_client_socket(const char *host, int port,
|
||||
time_t timeout_sec,
|
||||
time_t timeout_sec, time_t timeout_usec,
|
||||
const std::string &intf) {
|
||||
return create_socket(
|
||||
host, port, [&](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
@@ -1995,7 +2007,7 @@ inline socket_t create_client_socket(const char *host, int port,
|
||||
::connect(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
|
||||
if (ret < 0) {
|
||||
if (is_connection_error() ||
|
||||
!wait_until_socket_is_ready(sock, timeout_sec, 0)) {
|
||||
!wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) {
|
||||
close_socket(sock);
|
||||
return false;
|
||||
}
|
||||
@@ -2394,7 +2406,8 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) {
|
||||
|
||||
template <typename T>
|
||||
bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
Progress progress, ContentReceiver receiver) {
|
||||
Progress progress, ContentReceiver receiver,
|
||||
bool decompress) {
|
||||
|
||||
ContentReceiver out = [&](const char *buf, size_t n) {
|
||||
return receiver(buf, n);
|
||||
@@ -2402,26 +2415,31 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
decompressor decompressor;
|
||||
#endif
|
||||
|
||||
std::string content_encoding = x.get_header_value("Content-Encoding");
|
||||
if (content_encoding.find("gzip") != std::string::npos ||
|
||||
content_encoding.find("deflate") != std::string::npos) {
|
||||
if (!decompressor.is_valid()) {
|
||||
status = 500;
|
||||
if (decompress) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
std::string content_encoding = x.get_header_value("Content-Encoding");
|
||||
if (content_encoding.find("gzip") != std::string::npos ||
|
||||
content_encoding.find("deflate") != std::string::npos) {
|
||||
if (!decompressor.is_valid()) {
|
||||
status = 500;
|
||||
return false;
|
||||
}
|
||||
|
||||
out = [&](const char *buf, size_t n) {
|
||||
return decompressor.decompress(buf, n, [&](const char *buf, size_t n) {
|
||||
return receiver(buf, n);
|
||||
});
|
||||
};
|
||||
}
|
||||
#else
|
||||
if (x.get_header_value("Content-Encoding") == "gzip") {
|
||||
status = 415;
|
||||
return false;
|
||||
}
|
||||
|
||||
out = [&](const char *buf, size_t n) {
|
||||
return decompressor.decompress(
|
||||
buf, n, [&](const char *buf, size_t n) { return receiver(buf, n); });
|
||||
};
|
||||
}
|
||||
#else
|
||||
if (x.get_header_value("Content-Encoding") == "gzip") {
|
||||
status = 415;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
auto ret = true;
|
||||
auto exceed_payload_max_length = false;
|
||||
@@ -3033,7 +3051,8 @@ get_range_offset_and_length(const Request &req, const Response &res,
|
||||
|
||||
inline bool expect_content(const Request &req) {
|
||||
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
|
||||
req.method == "PRI" || req.method == "DELETE") {
|
||||
req.method == "PRI" ||
|
||||
(req.method == "DELETE" && req.has_header("Content-Length"))) {
|
||||
return true;
|
||||
}
|
||||
// TODO: check if Content-Length is set
|
||||
@@ -3415,12 +3434,12 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
if (!is_readable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
if (size > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
|
||||
return -1;
|
||||
}
|
||||
return recv(sock_, ptr, static_cast<int>(size), 0);
|
||||
#else
|
||||
return HANDLE_EINTR(recv, sock_, ptr, size, 0);
|
||||
return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); });
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3428,12 +3447,12 @@ inline ssize_t SocketStream::write(const char *ptr, size_t size) {
|
||||
if (!is_writable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
if (size > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
|
||||
return -1;
|
||||
}
|
||||
return send(sock_, ptr, static_cast<int>(size), 0);
|
||||
#else
|
||||
return HANDLE_EINTR(send, sock_, ptr, size, 0);
|
||||
return handle_EINTR([&]() { return send(sock_, ptr, size, 0); });
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3881,7 +3900,7 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
|
||||
}
|
||||
|
||||
if (!detail::read_content(strm, req, payload_max_length_, res.status,
|
||||
Progress(), out)) {
|
||||
Progress(), out, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3974,15 +3993,18 @@ inline bool Server::listen_internal() {
|
||||
std::unique_ptr<TaskQueue> task_queue(new_task_queue());
|
||||
|
||||
while (svr_sock_ != INVALID_SOCKET) {
|
||||
#ifndef _WIN32
|
||||
if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
|
||||
#endif
|
||||
auto val = detail::select_read(svr_sock_, idle_interval_sec_,
|
||||
idle_interval_usec_);
|
||||
if (val == 0) { // Timeout
|
||||
task_queue->on_idle();
|
||||
continue;
|
||||
}
|
||||
#ifndef _WIN32
|
||||
}
|
||||
|
||||
#endif
|
||||
socket_t sock = accept(svr_sock_, nullptr, nullptr);
|
||||
|
||||
if (sock == INVALID_SOCKET) {
|
||||
@@ -4231,10 +4253,12 @@ inline bool Client::is_valid() const { return true; }
|
||||
inline socket_t Client::create_client_socket() const {
|
||||
if (!proxy_host_.empty()) {
|
||||
return detail::create_client_socket(proxy_host_.c_str(), proxy_port_,
|
||||
timeout_sec_, interface_);
|
||||
connection_timeout_sec_,
|
||||
connection_timeout_usec_, interface_);
|
||||
}
|
||||
return detail::create_client_socket(host_.c_str(), port_, timeout_sec_,
|
||||
interface_);
|
||||
return detail::create_client_socket(host_.c_str(), port_,
|
||||
connection_timeout_sec_,
|
||||
connection_timeout_usec_, interface_);
|
||||
}
|
||||
|
||||
inline bool Client::read_response_line(Stream &strm, Response &res) {
|
||||
@@ -4656,7 +4680,7 @@ inline bool Client::process_request(Stream &strm, const Request &req,
|
||||
|
||||
int dummy_status;
|
||||
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
|
||||
dummy_status, req.progress, out)) {
|
||||
dummy_status, req.progress, out, decompress_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -4979,7 +5003,12 @@ inline void Client::stop() {
|
||||
}
|
||||
|
||||
inline void Client::set_timeout_sec(time_t timeout_sec) {
|
||||
timeout_sec_ = timeout_sec;
|
||||
set_connection_timeout(timeout_sec, 0);
|
||||
}
|
||||
|
||||
inline void Client::set_connection_timeout(time_t sec, time_t usec) {
|
||||
connection_timeout_sec_ = sec;
|
||||
connection_timeout_usec_ = usec;
|
||||
}
|
||||
|
||||
inline void Client::set_read_timeout(time_t sec, time_t usec) {
|
||||
@@ -5013,6 +5042,8 @@ inline void Client::set_follow_location(bool on) { follow_location_ = on; }
|
||||
|
||||
inline void Client::set_compress(bool on) { compress_ = on; }
|
||||
|
||||
inline void Client::set_decompress(bool on) { decompress_ = on; }
|
||||
|
||||
inline void Client::set_interface(const char *intf) { interface_ = intf; }
|
||||
|
||||
inline void Client::set_proxy(const char *host, int port) {
|
||||
@@ -5555,12 +5586,6 @@ inline bool SSLClient::check_host_name(const char *pattern,
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace detail {
|
||||
|
||||
#undef HANDLE_EINTR
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
} // namespace httplib
|
||||
|
||||
+16
-21
@@ -1,33 +1,28 @@
|
||||
# Generates a macro to auto-configure everything
|
||||
@PACKAGE_INIT@
|
||||
|
||||
# Setting these here so they're accessible after install.
|
||||
# Might be useful for some users to check which settings were used.
|
||||
set(HTTPLIB_IS_USING_OPENSSL @HTTPLIB_IS_USING_OPENSSL@)
|
||||
set(HTTPLIB_IS_USING_ZLIB @HTTPLIB_IS_USING_ZLIB@)
|
||||
set(HTTPLIB_IS_COMPILED @HTTPLIB_COMPILE@)
|
||||
|
||||
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)
|
||||
if(@HTTPLIB_IS_USING_OPENSSL@)
|
||||
# OpenSSL COMPONENTS were added in Cmake v3.11
|
||||
if(CMAKE_VERSION VERSION_LESS "3.11")
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ REQUIRED)
|
||||
else()
|
||||
# Once the COMPONENTS were added, they were made optional when not specified.
|
||||
# Since we use both, we need to search for both.
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ COMPONENTS Crypto SSL REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
if(@HTTPLIB_REQUIRE_ZLIB@)
|
||||
if(@HTTPLIB_IS_USING_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
|
||||
|
||||
+30
-15
@@ -245,7 +245,7 @@ TEST(ChunkedEncodingTest, FromHTTPWatch) {
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
cli.set_connection_timeout(2);
|
||||
|
||||
auto res =
|
||||
cli.Get("/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137");
|
||||
@@ -268,7 +268,7 @@ TEST(ChunkedEncodingTest, WithContentReceiver) {
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
cli.set_connection_timeout(2);
|
||||
|
||||
std::string body;
|
||||
auto res =
|
||||
@@ -296,7 +296,7 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver) {
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
cli.set_connection_timeout(2);
|
||||
|
||||
std::string body;
|
||||
auto res = cli.Get(
|
||||
@@ -328,7 +328,7 @@ TEST(RangeTest, FromHTTPBin) {
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(5);
|
||||
cli.set_connection_timeout(5);
|
||||
|
||||
{
|
||||
httplib::Headers headers;
|
||||
@@ -388,7 +388,7 @@ TEST(ConnectionErrorTest, InvalidHost) {
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
cli.set_connection_timeout(2);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
@@ -404,7 +404,7 @@ TEST(ConnectionErrorTest, InvalidPort) {
|
||||
auto port = 8080;
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
cli.set_connection_timeout(2);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
@@ -420,7 +420,7 @@ TEST(ConnectionErrorTest, Timeout) {
|
||||
auto port = 8080;
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(2);
|
||||
cli.set_connection_timeout(2);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
@@ -436,7 +436,7 @@ TEST(CancelTest, NoCancel) {
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(5);
|
||||
cli.set_connection_timeout(5);
|
||||
|
||||
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return true; });
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -456,7 +456,7 @@ TEST(CancelTest, WithCancelSmallPayload) {
|
||||
#endif
|
||||
|
||||
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return false; });
|
||||
cli.set_timeout_sec(5);
|
||||
cli.set_connection_timeout(5);
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
}
|
||||
|
||||
@@ -470,7 +470,7 @@ TEST(CancelTest, WithCancelLargePayload) {
|
||||
auto port = 80;
|
||||
httplib::Client cli(host, port);
|
||||
#endif
|
||||
cli.set_timeout_sec(5);
|
||||
cli.set_connection_timeout(5);
|
||||
|
||||
uint32_t count = 0;
|
||||
httplib::Headers headers;
|
||||
@@ -2206,6 +2206,21 @@ TEST_F(ServerTest, GzipWithContentReceiver) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GzipWithoutDecompressing) {
|
||||
Headers headers;
|
||||
headers.emplace("Accept-Encoding", "gzip, deflate");
|
||||
|
||||
cli_.set_decompress(false);
|
||||
auto res = cli_.Get("/gzip", headers);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("gzip", res->get_header_value("Content-Encoding"));
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("33", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(33, res->body.size());
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
|
||||
Headers headers;
|
||||
std::string body;
|
||||
@@ -2279,7 +2294,7 @@ TEST_F(ServerTest, MultipartFormDataGzip) {
|
||||
// Sends a raw request to a server listening at HOST:PORT.
|
||||
static bool send_request(time_t read_timeout_sec, const std::string &req,
|
||||
std::string *resp = nullptr) {
|
||||
auto client_sock = detail::create_client_socket(HOST, PORT, /*timeout_sec=*/5,
|
||||
auto client_sock = detail::create_client_socket(HOST, PORT, /*timeout_sec=*/5, 0,
|
||||
std::string());
|
||||
|
||||
if (client_sock == INVALID_SOCKET) { return false; }
|
||||
@@ -2774,7 +2789,7 @@ TEST(SSLClientServerTest, ClientCertPresent) {
|
||||
|
||||
httplib::SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
|
||||
auto res = cli.Get("/test");
|
||||
cli.set_timeout_sec(30);
|
||||
cli.set_connection_timeout(30);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
|
||||
@@ -2843,7 +2858,7 @@ TEST(SSLClientServerTest, MemoryClientCertPresent) {
|
||||
|
||||
httplib::SSLClient cli(HOST, PORT, client_cert, client_private_key);
|
||||
auto res = cli.Get("/test");
|
||||
cli.set_timeout_sec(30);
|
||||
cli.set_connection_timeout(30);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
|
||||
@@ -2867,7 +2882,7 @@ TEST(SSLClientServerTest, ClientCertMissing) {
|
||||
|
||||
httplib::SSLClient cli(HOST, PORT);
|
||||
auto res = cli.Get("/test");
|
||||
cli.set_timeout_sec(30);
|
||||
cli.set_connection_timeout(30);
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
|
||||
svr.stop();
|
||||
@@ -2889,7 +2904,7 @@ TEST(SSLClientServerTest, TrustDirOptional) {
|
||||
|
||||
httplib::SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
|
||||
auto res = cli.Get("/test");
|
||||
cli.set_timeout_sec(30);
|
||||
cli.set_connection_timeout(30);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user