mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| adf58bf474 | |||
| 337fbb0793 | |||
| 9e7861b0b4 | |||
| 27ee115a60 | |||
| 59882752aa | |||
| 61e9f7ce8f | |||
| 1acf18876f | |||
| 4b2b851dbb | |||
| 551f96d4a2 | |||
| eacc1ca98e | |||
| ac9ebb0ee3 | |||
| 11eed05ce7 | |||
| f3bba0646a | |||
| 2da189f88c | |||
| 6e0f211cff | |||
| 318a3fe425 | |||
| 2d8d524178 | |||
| afa88dbe70 | |||
| 08133b593b | |||
| 8aedbf4547 | |||
| cde29362ef | |||
| bae40fcdf2 | |||
| db561f5552 | |||
| 35c52c1ab9 | |||
| 23ff9a5605 | |||
| 41be1e24e3 | |||
| 6e52d0a057 | |||
| f72b4582e6 |
@@ -148,7 +148,7 @@ jobs:
|
||||
run: vcpkg install gtest curl zlib brotli zstd
|
||||
- name: Install OpenSSL
|
||||
if: ${{ matrix.config.with_ssl }}
|
||||
run: choco install openssl --version 3.5.2 # workaround for chocolatey issue with the latest OpenSSL
|
||||
run: choco install openssl
|
||||
- name: Configure CMake ${{ matrix.config.name }}
|
||||
run: >
|
||||
cmake -B build -S .
|
||||
|
||||
+15
-1
@@ -1,16 +1,30 @@
|
||||
tags
|
||||
|
||||
# Ignore executables (no extension) but not source files
|
||||
example/server
|
||||
!example/server.*
|
||||
example/client
|
||||
!example/client.*
|
||||
example/hello
|
||||
!example/hello.*
|
||||
example/simplecli
|
||||
!example/simplecli.*
|
||||
example/simplesvr
|
||||
!example/simplesvr.*
|
||||
example/benchmark
|
||||
!example/benchmark.*
|
||||
example/redirect
|
||||
example/sse*
|
||||
!example/redirect.*
|
||||
example/ssecli
|
||||
example/ssesvr
|
||||
example/upload
|
||||
!example/upload.*
|
||||
example/one_time_request
|
||||
!example/one_time_request.*
|
||||
example/server_and_client
|
||||
!example/server_and_client.*
|
||||
example/accept_header
|
||||
!example/accept_header.*
|
||||
example/*.pem
|
||||
test/httplib.cc
|
||||
test/httplib.h
|
||||
|
||||
+26
-11
@@ -1,6 +1,6 @@
|
||||
#[[
|
||||
Build options:
|
||||
* BUILD_SHARED_LIBS (default off) builds as a shared library (if HTTPLIB_COMPILE is ON)
|
||||
* Standard BUILD_SHARED_LIBS is supported and sets HTTPLIB_SHARED default value.
|
||||
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
@@ -13,6 +13,7 @@
|
||||
* HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_INSTALL (default on)
|
||||
* HTTPLIB_SHARED (default off) builds as a shared library (if HTTPLIB_COMPILE is ON)
|
||||
* HTTPLIB_TEST (default off)
|
||||
* 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).
|
||||
@@ -109,16 +110,31 @@ option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system cer
|
||||
option(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO "Enables the non-blocking alternatives for getaddrinfo." ON)
|
||||
option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
|
||||
# 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)
|
||||
# Defaults to static library but respects standard BUILD_SHARED_LIBS if set
|
||||
include(CMakeDependentOption)
|
||||
cmake_dependent_option(HTTPLIB_SHARED "Build the library as a shared library instead of static. Has no effect if using header-only."
|
||||
"${BUILD_SHARED_LIBS}" HTTPLIB_COMPILE OFF
|
||||
)
|
||||
if(HTTPLIB_SHARED)
|
||||
set(HTTPLIB_LIB_TYPE SHARED)
|
||||
if(WIN32)
|
||||
# Necessary for Windows if building shared libs
|
||||
# See https://stackoverflow.com/a/40743080
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
endif()
|
||||
else()
|
||||
set(HTTPLIB_LIB_TYPE STATIC)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Windows" AND ${CMAKE_SYSTEM_VERSION} VERSION_LESS "10.0.0")
|
||||
message(SEND_ERROR "Windows ${CMAKE_SYSTEM_VERSION} or lower is not supported. Please use Windows 10 or later.")
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
if(CMAKE_SYSTEM_VERSION)
|
||||
if(${CMAKE_SYSTEM_VERSION} VERSION_LESS "10.0.0")
|
||||
message(SEND_ERROR "Windows ${CMAKE_SYSTEM_VERSION} or lower is not supported. Please use Windows 10 or later.")
|
||||
endif()
|
||||
else()
|
||||
set(CMAKE_SYSTEM_VERSION "10.0.19041.0")
|
||||
message(WARNING "The target is Windows but CMAKE_SYSTEM_VERSION is not set, the default system version is set to Windows 10.")
|
||||
endif()
|
||||
endif()
|
||||
if(CMAKE_SIZEOF_VOID_P LESS 8)
|
||||
message(WARNING "Pointer size ${CMAKE_SIZEOF_VOID_P} is not supported. Please use a 64-bit compiler.")
|
||||
@@ -223,8 +239,7 @@ if(HTTPLIB_COMPILE)
|
||||
|
||||
# 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")
|
||||
add_library(${PROJECT_NAME} ${HTTPLIB_LIB_TYPE} "${_httplib_build_includedir}/httplib.cc")
|
||||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
|
||||
|
||||
@@ -44,9 +44,10 @@ httplib::Client cli("http://yhirose.github.io");
|
||||
// HTTPS
|
||||
httplib::Client cli("https://yhirose.github.io");
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
res->status;
|
||||
res->body;
|
||||
if (auto res = cli.Get("/hi")) {
|
||||
res->status;
|
||||
res->body;
|
||||
}
|
||||
```
|
||||
|
||||
SSL Support
|
||||
@@ -190,7 +191,7 @@ int main(void)
|
||||
}
|
||||
```
|
||||
|
||||
`Post`, `Put`, `Delete` and `Options` methods are also supported.
|
||||
`Post`, `Put`, `Patch`, `Delete` and `Options` methods are also supported.
|
||||
|
||||
### Bind a socket to multiple interfaces and any available port
|
||||
|
||||
@@ -853,6 +854,12 @@ auto res = cli.Post("/multipart", items);
|
||||
res = cli.Put("/resource/foo", "text", "text/plain");
|
||||
```
|
||||
|
||||
### PATCH
|
||||
|
||||
```c++
|
||||
res = cli.Patch("/resource/foo", "text", "text/plain");
|
||||
```
|
||||
|
||||
### DELETE
|
||||
|
||||
```c++
|
||||
|
||||
@@ -86,7 +86,9 @@ foreach(_listvar "common;common" "decoder;dec" "encoder;enc")
|
||||
# Check if the target was already found by Pkgconf
|
||||
if(TARGET PkgConfig::Brotli_${_component_name}${_brotli_stat_str})
|
||||
# ALIAS since we don't want the PkgConfig namespace on the Cmake library (for end-users)
|
||||
add_library(Brotli::${_component_name} ALIAS PkgConfig::Brotli_${_component_name}${_brotli_stat_str})
|
||||
if (NOT TARGET Brotli::${_component_name})
|
||||
add_library(Brotli::${_component_name} ALIAS PkgConfig::Brotli_${_component_name}${_brotli_stat_str})
|
||||
endif()
|
||||
|
||||
# Tells HANDLE_COMPONENTS we found the component
|
||||
set(Brotli_${_component_name}_FOUND TRUE)
|
||||
@@ -139,7 +141,10 @@ foreach(_listvar "common;common" "decoder;dec" "encoder;enc")
|
||||
# Tells HANDLE_COMPONENTS we found the component
|
||||
set(Brotli_${_component_name}_FOUND TRUE)
|
||||
|
||||
add_library("Brotli::${_component_name}" UNKNOWN IMPORTED)
|
||||
if (NOT TARGET Brotli::${_component_name})
|
||||
add_library("Brotli::${_component_name}" UNKNOWN IMPORTED)
|
||||
endif()
|
||||
|
||||
# Attach the literal library and include dir to the IMPORTED target for the end-user
|
||||
set_target_properties("Brotli::${_component_name}" PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Brotli_INCLUDE_DIR}"
|
||||
|
||||
+34
-26
@@ -48,32 +48,10 @@ std::string get_error_time_format() {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string get_client_ip(const Request &req) {
|
||||
// Check for X-Forwarded-For header first (common in reverse proxy setups)
|
||||
auto forwarded_for = req.get_header_value("X-Forwarded-For");
|
||||
if (!forwarded_for.empty()) {
|
||||
// Get the first IP if there are multiple
|
||||
auto comma_pos = forwarded_for.find(',');
|
||||
if (comma_pos != std::string::npos) {
|
||||
return forwarded_for.substr(0, comma_pos);
|
||||
}
|
||||
return forwarded_for;
|
||||
}
|
||||
|
||||
// Check for X-Real-IP header
|
||||
auto real_ip = req.get_header_value("X-Real-IP");
|
||||
if (!real_ip.empty()) { return real_ip; }
|
||||
|
||||
// Fallback to remote address (though cpp-httplib doesn't provide this
|
||||
// directly) For demonstration, we'll use a placeholder
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
// NGINX Combined log format:
|
||||
// $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent
|
||||
// "$http_referer" "$http_user_agent"
|
||||
void nginx_access_logger(const Request &req, const Response &res) {
|
||||
auto remote_addr = get_client_ip(req);
|
||||
std::string remote_user =
|
||||
"-"; // cpp-httplib doesn't have built-in auth user tracking
|
||||
auto time_local = get_time_format();
|
||||
@@ -86,7 +64,7 @@ void nginx_access_logger(const Request &req, const Response &res) {
|
||||
if (http_user_agent.empty()) http_user_agent = "-";
|
||||
|
||||
std::cout << std::format("{} - {} [{}] \"{}\" {} {} \"{}\" \"{}\"",
|
||||
remote_addr, remote_user, time_local, request,
|
||||
req.remote_addr, remote_user, time_local, request,
|
||||
status, body_bytes_sent, http_referer,
|
||||
http_user_agent)
|
||||
<< std::endl;
|
||||
@@ -100,7 +78,6 @@ void nginx_error_logger(const Error &err, const Request *req) {
|
||||
std::string level = "error";
|
||||
|
||||
if (req) {
|
||||
auto client_ip = get_client_ip(*req);
|
||||
auto request =
|
||||
std::format("{} {} {}", req->method, req->path, req->version);
|
||||
auto host = req->get_header_value("Host");
|
||||
@@ -108,8 +85,8 @@ void nginx_error_logger(const Error &err, const Request *req) {
|
||||
|
||||
std::cerr << std::format("{} [{}] {}, client: {}, request: "
|
||||
"\"{}\", host: \"{}\"",
|
||||
time_local, level, to_string(err), client_ip,
|
||||
request, host)
|
||||
time_local, level, to_string(err),
|
||||
req->remote_addr, request, host)
|
||||
<< std::endl;
|
||||
} else {
|
||||
// If no request context, just log the error
|
||||
@@ -131,6 +108,10 @@ void print_usage(const char *program_name) {
|
||||
std::cout << " Format: mount_point:document_root"
|
||||
<< std::endl;
|
||||
std::cout << " (default: /:./html)" << std::endl;
|
||||
std::cout << " --trusted-proxy <ip> Add trusted proxy IP address"
|
||||
<< std::endl;
|
||||
std::cout << " (can be specified multiple times)"
|
||||
<< std::endl;
|
||||
std::cout << " --version Show version information"
|
||||
<< std::endl;
|
||||
std::cout << " --help Show this help message" << std::endl;
|
||||
@@ -140,6 +121,9 @@ void print_usage(const char *program_name) {
|
||||
<< " --host localhost --port 8080 --mount /:./html" << std::endl;
|
||||
std::cout << " " << program_name
|
||||
<< " --host 0.0.0.0 --port 3000 --mount /api:./api" << std::endl;
|
||||
std::cout << " " << program_name
|
||||
<< " --trusted-proxy 192.168.1.100 --trusted-proxy 10.0.0.1"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
struct ServerConfig {
|
||||
@@ -147,6 +131,7 @@ struct ServerConfig {
|
||||
int port = 8080;
|
||||
std::string mount_point = "/";
|
||||
std::string document_root = "./html";
|
||||
std::vector<std::string> trusted_proxies;
|
||||
};
|
||||
|
||||
enum class ParseResult { SUCCESS, HELP_REQUESTED, VERSION_REQUESTED, ERROR };
|
||||
@@ -205,6 +190,14 @@ ParseResult parse_command_line(int argc, char *argv[], ServerConfig &config) {
|
||||
} else if (strcmp(argv[i], "--version") == 0) {
|
||||
std::cout << CPPHTTPLIB_VERSION << std::endl;
|
||||
return ParseResult::VERSION_REQUESTED;
|
||||
} else if (strcmp(argv[i], "--trusted-proxy") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr << "Error: --trusted-proxy requires an IP address argument"
|
||||
<< std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
config.trusted_proxies.push_back(argv[++i]);
|
||||
} else {
|
||||
std::cerr << "Error: Unknown option '" << argv[i] << "'" << std::endl;
|
||||
print_usage(argv[0]);
|
||||
@@ -218,6 +211,11 @@ bool setup_server(Server &svr, const ServerConfig &config) {
|
||||
svr.set_logger(nginx_access_logger);
|
||||
svr.set_error_logger(nginx_error_logger);
|
||||
|
||||
// Set trusted proxies if specified
|
||||
if (!config.trusted_proxies.empty()) {
|
||||
svr.set_trusted_proxies(config.trusted_proxies);
|
||||
}
|
||||
|
||||
auto ret = svr.set_mount_point(config.mount_point, config.document_root);
|
||||
if (!ret) {
|
||||
std::cerr
|
||||
@@ -285,6 +283,16 @@ int main(int argc, char *argv[]) {
|
||||
<< std::endl;
|
||||
std::cout << "Mount point: " << config.mount_point << " -> "
|
||||
<< config.document_root << std::endl;
|
||||
|
||||
if (!config.trusted_proxies.empty()) {
|
||||
std::cout << "Trusted proxies: ";
|
||||
for (size_t i = 0; i < config.trusted_proxies.size(); ++i) {
|
||||
if (i > 0) std::cout << ", ";
|
||||
std::cout << config.trusted_proxies[i];
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "Press Ctrl+C to shutdown gracefully..." << std::endl;
|
||||
|
||||
auto ret = svr.listen(config.hostname, config.port);
|
||||
|
||||
+11
-6
@@ -14,11 +14,18 @@ class EventDispatcher {
|
||||
public:
|
||||
EventDispatcher() {}
|
||||
|
||||
void wait_event(DataSink *sink) {
|
||||
bool wait_event(DataSink *sink) {
|
||||
unique_lock<mutex> lk(m_);
|
||||
int id = id_;
|
||||
cv_.wait(lk, [&] { return cid_ == id; });
|
||||
|
||||
// Wait with timeout to prevent hanging if client disconnects
|
||||
if (!cv_.wait_for(lk, std::chrono::seconds(5),
|
||||
[&] { return cid_ == id; })) {
|
||||
return false; // Timeout occurred
|
||||
}
|
||||
|
||||
sink->write(message_.data(), message_.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void send_event(const string &message) {
|
||||
@@ -71,8 +78,7 @@ int main(void) {
|
||||
cout << "connected to event1..." << endl;
|
||||
res.set_chunked_content_provider("text/event-stream",
|
||||
[&](size_t /*offset*/, DataSink &sink) {
|
||||
ed.wait_event(&sink);
|
||||
return true;
|
||||
return ed.wait_event(&sink);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,8 +86,7 @@ int main(void) {
|
||||
cout << "connected to event2..." << endl;
|
||||
res.set_chunked_content_provider("text/event-stream",
|
||||
[&](size_t /*offset*/, DataSink &sink) {
|
||||
ed.wait_event(&sink);
|
||||
return true;
|
||||
return ed.wait_event(&sink);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+4
-2
@@ -108,9 +108,11 @@ subdir('www')
|
||||
subdir('www2'/'dir')
|
||||
subdir('www3'/'dir')
|
||||
|
||||
# GoogleTest 1.13.0 requires C++14
|
||||
# New GoogleTest versions require new C++ standards
|
||||
test_options = []
|
||||
if gtest_dep.version().version_compare('>=1.13.0')
|
||||
if gtest_dep.version().version_compare('>=1.17.0')
|
||||
test_options += 'cpp_std=c++17'
|
||||
elif gtest_dep.version().version_compare('>=1.13.0')
|
||||
test_options += 'cpp_std=c++14'
|
||||
endif
|
||||
|
||||
|
||||
+930
-58
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user