Compare commits

...
24 Commits
Author SHA1 Message Date
yhirose 89c932f313 Release v0.26.0 2025-08-29 16:05:44 -04:00
yhirose eb5a65e0df Fix #2217 2025-08-29 15:01:59 -04:00
kgokalp 7a3b92bbd9 Fix: handle EAI_ALLDONE from gai_suspend in getaddrinfo_with_timeout (#2228) 2025-08-28 11:08:32 -04:00
yhirose eb11032797 Fix platform problem 2025-08-27 00:31:14 -04:00
yhirose 54e75dc8ef Add manual run 2025-08-26 23:34:18 -04:00
yhirose b20b5fdd1f Add 'release-docker' workflow 2025-08-26 23:18:59 -04:00
yhirose f4cc542d4b Fix Dockerfile problem with CMD 2025-08-26 22:17:54 -04:00
yhirose 4285d33992 Fix #2223 (#2224)
* Fix #2223

* Fix build error
2025-08-26 21:42:13 -04:00
yhirose 92b4f53012 clang-format 2025-08-26 20:29:43 -04:00
tejas b8e21eac89 Initialize start time for server (#2220)
* Initialize start time for server

By initializing start_time_ for server, I hope to measure the time taken to process a request at the end maybe in the set_logger callback and print it.

I only see current usage in client with server retaining the inital min value

* Add test to verify start time is initialized

* Address review comments

* run clang format
2025-08-26 15:34:13 -04:00
Sergey 3fae5f1473 osx: fix inconsistent use of the macro TARGET_OS_OSX (#2222)
* osx: fix inconsistent use of the macro `TARGET_OS_OSX`

Fixed the build error on iOS:

```
httplib.h:3583:3: error: unknown type name 'CFStringRef'
  870 |   CFStringRef hostname_ref = CFStringCreateWithCString(
```

Note, `TARGET_OS_OSX` is defined but is 0 when `TARGET_OS_IOS` is 1,
and vise versa. Hence, `TARGET_OS_MAC` should have been used, that is
set to 1 for the both targets.

* improve: non-blocking getaddrinfo() for all mac target variants

`TARGET_OS_MAC` should have been used, that is set to 1 for all other
targets: OSX, IPHONE (IOS, TV, WATCH, VISION, BRIDGE), SIMULATOR,
DRIVERKIT.
2025-08-26 12:46:51 -04:00
Andrea Pappacoda fe7fe15d2e build(meson): fix new build option names (#2208)
This is a follow-up to commit 4ff7a1c858,
which introduced new simplified build options and deprecated the old
ones. I forgot to also change the various get_option() calls,
effectively rendering the new option names useless, as they would not
get honoured.
2025-08-19 15:22:08 -04:00
Thomas Beutlich fbd6ce7a3f Make code sample compilable (#2207) 2025-08-14 06:57:39 -04:00
Thomas Beutlich dffce89514 #2201 Fix 32-bit MSVC compiler error due to unknown command #warning (#2202) 2025-08-12 17:06:09 -04:00
yhirose 3f44c80fd3 Release v0.25.0 2025-08-07 20:58:39 -04:00
yhirose a2bb6f6c1e Update docker/main.cc 2025-08-07 20:57:37 -04:00
Thomas Beutlich 7012e765e1 CMake: Check pointer size at configure time (#2197)
* Check pointer size at configure time

* Relax check to warning
2025-08-07 17:14:19 -04:00
yhirose b1c1fa2dc6 Code cleanup 2025-08-07 00:09:09 -04:00
yhirose fbee136dca Fix #2193. Allow _WIN32 2025-08-06 23:12:33 -04:00
yhirose 70cca55caf Workaround for chocolatey issue with the latest OpenSSL 2025-08-06 17:54:08 -04:00
yhirose cdaed14925 Update README 2025-08-06 17:41:03 -04:00
yhirose b52d7d8411 ErrorLogger support (#870) (#2195) 2025-08-06 17:38:18 -04:00
Thomas Beutlich acf28a362d #2191 Check for minimum required Windows version (#2192) 2025-08-01 20:16:43 -04:00
yhirose 0b3758ec36 Fix problem with Windows version check 2025-07-30 17:39:40 -04:00
9 changed files with 786 additions and 151 deletions
+51
View File
@@ -0,0 +1,51 @@
name: Release Docker Image
on:
release:
types: [published]
workflow_dispatch:
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history and tags
- name: Extract tag (manual)
if: github.event_name == 'workflow_dispatch'
id: set_tag_manual
run: |
# Checkout the latest tag and set output
git fetch --tags
LATEST_TAG=$(git describe --tags --abbrev=0)
git checkout $LATEST_TAG
echo "tag=${LATEST_TAG#v}" >> $GITHUB_OUTPUT
- name: Extract tag (release)
if: github.event_name == 'release'
id: set_tag_release
run: echo "tag=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
platforms: linux/amd64,linux/arm64 # Build for both amd64 and arm64
# Use extracted tag without leading 'v'
tags: |
yhirose4dockerhub/cpp-httplib-server:latest
yhirose4dockerhub/cpp-httplib-server:${{ steps.set_tag_manual.outputs.tag || steps.set_tag_release.outputs.tag }}
+1 -1
View File
@@ -148,7 +148,7 @@ jobs:
run: vcpkg install gtest curl zlib brotli zstd run: vcpkg install gtest curl zlib brotli zstd
- name: Install OpenSSL - name: Install OpenSSL
if: ${{ matrix.config.with_ssl }} if: ${{ matrix.config.with_ssl }}
run: choco install openssl run: choco install openssl --version 3.5.2 # workaround for chocolatey issue with the latest OpenSSL
- name: Configure CMake ${{ matrix.config.name }} - name: Configure CMake ${{ matrix.config.name }}
run: > run: >
cmake -B build -S . cmake -B build -S .
+12 -5
View File
@@ -111,12 +111,19 @@ option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build.
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON) option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
# Defaults to static library # 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) 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) if(BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
# Necessary for Windows if building shared libs # Necessary for Windows if building shared libs
# See https://stackoverflow.com/a/40743080 # See https://stackoverflow.com/a/40743080
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif() 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.")
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.")
endif()
# Set some variables that are used in-tree and while building based on our options # Set some variables that are used in-tree and while building based on our options
set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE}) set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE})
set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN ${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN}) set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN ${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN})
@@ -243,8 +250,8 @@ add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11) target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11)
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC} 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}>
) )
# Always require threads # Always require threads
@@ -340,6 +347,6 @@ if(HTTPLIB_INSTALL)
endif() endif()
if(HTTPLIB_TEST) if(HTTPLIB_TEST)
include(CTest) include(CTest)
add_subdirectory(test) add_subdirectory(test)
endif() endif()
+3 -1
View File
@@ -8,4 +8,6 @@ FROM scratch
COPY --from=builder /build/server /server COPY --from=builder /build/server /server
COPY docker/html/index.html /html/index.html COPY docker/html/index.html /html/index.html
EXPOSE 80 EXPOSE 80
CMD ["/server"]
ENTRYPOINT ["/server"]
CMD ["--host", "0.0.0.0", "--port", "80", "--mount", "/:./html"]
+79 -11
View File
@@ -98,32 +98,31 @@ httplib::Client cli("https://example.com");
auto res = cli.Get("/"); auto res = cli.Get("/");
if (!res) { if (!res) {
// Check the error type // Check the error type
auto err = res.error(); const auto err = res.error();
switch (err) { switch (err) {
case httplib::Error::SSLConnection: case httplib::Error::SSLConnection:
std::cout << "SSL connection failed, SSL error: " std::cout << "SSL connection failed, SSL error: "
<< res->ssl_error() << std::endl; << res.ssl_error() << std::endl;
break; break;
case httplib::Error::SSLLoadingCerts: case httplib::Error::SSLLoadingCerts:
std::cout << "SSL cert loading failed, OpenSSL error: " std::cout << "SSL cert loading failed, OpenSSL error: "
<< std::hex << res->ssl_openssl_error() << std::endl; << std::hex << res.ssl_openssl_error() << std::endl;
break; break;
case httplib::Error::SSLServerVerification: case httplib::Error::SSLServerVerification:
std::cout << "SSL verification failed, X509 error: " std::cout << "SSL verification failed, X509 error: "
<< res->ssl_openssl_error() << std::endl; << res.ssl_openssl_error() << std::endl;
break; break;
case httplib::Error::SSLServerHostnameVerification: case httplib::Error::SSLServerHostnameVerification:
std::cout << "SSL hostname verification failed, X509 error: " std::cout << "SSL hostname verification failed, X509 error: "
<< res->ssl_openssl_error() << std::endl; << res.ssl_openssl_error() << std::endl;
break; break;
default: default:
std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
}
} }
} }
``` ```
@@ -269,23 +268,47 @@ svr.set_file_request_handler([](const Request &req, Response &res) {
### Logging ### Logging
cpp-httplib provides separate logging capabilities for access logs and error logs, similar to web servers like Nginx and Apache.
#### Access Logging
Access loggers capture successful HTTP requests and responses:
```cpp ```cpp
svr.set_logger([](const auto& req, const auto& res) { svr.set_logger([](const httplib::Request& req, const httplib::Response& res) {
your_logger(req, res); std::cout << req.method << " " << req.path << " -> " << res.status << std::endl;
}); });
``` ```
You can also set a pre-compression logger to capture request/response data before compression is applied. This is useful for debugging and monitoring purposes when you need to see the original, uncompressed response content: #### Pre-compression Logging
You can also set a pre-compression logger to capture request/response data before compression is applied:
```cpp ```cpp
svr.set_pre_compression_logger([](const auto& req, const auto& res) { svr.set_pre_compression_logger([](const httplib::Request& req, const httplib::Response& res) {
// Log before compression - res.body contains uncompressed content // Log before compression - res.body contains uncompressed content
// Content-Encoding header is not yet set // Content-Encoding header is not yet set
your_pre_compression_logger(req, res); your_pre_compression_logger(req, res);
}); });
``` ```
The pre-compression logger is only called when compression would be applied. For responses without compression, only the regular logger is called. The pre-compression logger is only called when compression would be applied. For responses without compression, only the access logger is called.
#### Error Logging
Error loggers capture failed requests and connection issues. Unlike access loggers, error loggers only receive the Error and Request information, as errors typically occur before a meaningful Response can be generated.
```cpp
svr.set_error_logger([](const httplib::Error& err, const httplib::Request* req) {
std::cerr << httplib::to_string(err) << " while processing request";
if (req) {
std::cerr << ", client: " << req->get_header_value("X-Forwarded-For")
<< ", request: '" << req->method << " " << req->path << " " << req->version << "'"
<< ", host: " << req->get_header_value("Host");
}
std::cerr << std::endl;
});
```
### Error handler ### Error handler
@@ -718,6 +741,51 @@ enum Error {
}; };
``` ```
### Client Logging
#### Access Logging
```cpp
cli.set_logger([](const httplib::Request& req, const httplib::Response& res) {
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start_time).count();
std::cout << "✓ " << req.method << " " << req.path
<< " -> " << res.status << " (" << res.body.size() << " bytes, "
<< duration << "ms)" << std::endl;
});
```
#### Error Logging
```cpp
cli.set_error_logger([](const httplib::Error& err, const httplib::Request* req) {
std::cerr << "✗ ";
if (req) {
std::cerr << req->method << " " << req->path << " ";
}
std::cerr << "failed: " << httplib::to_string(err);
// Add specific guidance based on error type
switch (err) {
case httplib::Error::Connection:
std::cerr << " (verify server is running and reachable)";
break;
case httplib::Error::SSLConnection:
std::cerr << " (check SSL certificate and TLS configuration)";
break;
case httplib::Error::ConnectionTimeout:
std::cerr << " (increase timeout or check network latency)";
break;
case httplib::Error::Read:
std::cerr << " (server may have closed connection prematurely)";
break;
default:
break;
}
std::cerr << std::endl;
});
```
### GET with HTTP headers ### GET with HTTP headers
```c++ ```c++
+256 -42
View File
@@ -5,77 +5,291 @@
// MIT License // MIT License
// //
#include <atomic>
#include <chrono> #include <chrono>
#include <ctime> #include <ctime>
#include <format> #include <format>
#include <iomanip> #include <iomanip>
#include <iostream> #include <iostream>
#include <signal.h>
#include <sstream> #include <sstream>
#include <httplib.h> #include <httplib.h>
constexpr auto error_html = R"(<html> using namespace httplib;
<head><title>{} {}</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>cpp-httplib/{}</center>
</body>
</html>
)";
void sigint_handler(int s) { exit(1); } const auto SERVER_NAME =
std::format("cpp-httplib-server/{}", CPPHTTPLIB_VERSION);
std::string time_local() { Server svr;
auto p = std::chrono::system_clock::now();
auto t = std::chrono::system_clock::to_time_t(p); void signal_handler(int signal) {
if (signal == SIGINT || signal == SIGTERM) {
std::cout << "\nReceived signal, shutting down gracefully...\n";
svr.stop();
}
}
std::string get_time_format() {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss; std::stringstream ss;
ss << std::put_time(std::localtime(&t), "%d/%b/%Y:%H:%M:%S %z"); ss << std::put_time(std::localtime(&time_t), "%d/%b/%Y:%H:%M:%S %z");
return ss.str(); return ss.str();
} }
std::string log(auto &req, auto &res) { std::string get_error_time_format() {
auto remote_user = "-"; // TODO: auto now = std::chrono::system_clock::now();
auto request = std::format("{} {} {}", req.method, req.path, req.version); auto time_t = std::chrono::system_clock::to_time_t(now);
auto body_bytes_sent = res.get_header_value("Content-Length");
auto http_referer = "-"; // TODO:
auto http_user_agent = req.get_header_value("User-Agent", "-");
// NOTE: From NGINX default access log format std::stringstream ss;
// log_format combined '$remote_addr - $remote_user [$time_local] ' ss << std::put_time(std::localtime(&time_t), "%Y/%m/%d %H:%M:%S");
// '"$request" $status $body_bytes_sent ' return ss.str();
// '"$http_referer" "$http_user_agent"';
return std::format(R"({} - {} [{}] "{}" {} {} "{}" "{}")", req.remote_addr,
remote_user, time_local(), request, res.status,
body_bytes_sent, http_referer, http_user_agent);
} }
int main(int argc, const char **argv) { std::string get_client_ip(const Request &req) {
signal(SIGINT, sigint_handler); // 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;
}
auto base_dir = "./html"; // Check for X-Real-IP header
auto host = "0.0.0.0"; auto real_ip = req.get_header_value("X-Real-IP");
auto port = 80; if (!real_ip.empty()) { return real_ip; }
httplib::Server svr; // Fallback to remote address (though cpp-httplib doesn't provide this
// directly) For demonstration, we'll use a placeholder
return "127.0.0.1";
}
svr.set_error_handler([](auto & /*req*/, auto &res) { // NGINX Combined log format:
auto body = // $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent
std::format(error_html, res.status, httplib::status_message(res.status), // "$http_referer" "$http_user_agent"
CPPHTTPLIB_VERSION); 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();
auto request = std::format("{} {} {}", req.method, req.path, req.version);
auto status = res.status;
auto body_bytes_sent = res.body.size();
auto http_referer = req.get_header_value("Referer");
if (http_referer.empty()) http_referer = "-";
auto http_user_agent = req.get_header_value("User-Agent");
if (http_user_agent.empty()) http_user_agent = "-";
res.set_content(body, "text/html"); std::cout << std::format("{} - {} [{}] \"{}\" {} {} \"{}\" \"{}\"",
remote_addr, remote_user, time_local, request,
status, body_bytes_sent, http_referer,
http_user_agent)
<< std::endl;
}
// NGINX Error log format:
// YYYY/MM/DD HH:MM:SS [level] message, client: client_ip, request: "request",
// host: "host"
void nginx_error_logger(const Error &err, const Request *req) {
auto time_local = get_error_time_format();
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");
if (host.empty()) host = "-";
std::cerr << std::format("{} [{}] {}, client: {}, request: "
"\"{}\", host: \"{}\"",
time_local, level, to_string(err), client_ip,
request, host)
<< std::endl;
} else {
// If no request context, just log the error
std::cerr << std::format("{} [{}] {}", time_local, level, to_string(err))
<< std::endl;
}
}
void print_usage(const char *program_name) {
std::cout << "Usage: " << program_name << " [OPTIONS]" << std::endl;
std::cout << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " --host <hostname> Server hostname (default: localhost)"
<< std::endl;
std::cout << " --port <port> Server port (default: 8080)"
<< std::endl;
std::cout << " --mount <mount:path> Mount point and document root"
<< std::endl;
std::cout << " Format: mount_point:document_root"
<< std::endl;
std::cout << " (default: /:./html)" << std::endl;
std::cout << " --version Show version information"
<< std::endl;
std::cout << " --help Show this help message" << std::endl;
std::cout << std::endl;
std::cout << "Examples:" << std::endl;
std::cout << " " << 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;
}
struct ServerConfig {
std::string hostname = "localhost";
int port = 8080;
std::string mount_point = "/";
std::string document_root = "./html";
};
enum class ParseResult { SUCCESS, HELP_REQUESTED, VERSION_REQUESTED, ERROR };
ParseResult parse_command_line(int argc, char *argv[], ServerConfig &config) {
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
print_usage(argv[0]);
return ParseResult::HELP_REQUESTED;
} else if (strcmp(argv[i], "--host") == 0) {
if (i + 1 >= argc) {
std::cerr << "Error: --host requires a hostname argument" << std::endl;
print_usage(argv[0]);
return ParseResult::ERROR;
}
config.hostname = argv[++i];
} else if (strcmp(argv[i], "--port") == 0) {
if (i + 1 >= argc) {
std::cerr << "Error: --port requires a port number argument"
<< std::endl;
print_usage(argv[0]);
return ParseResult::ERROR;
}
config.port = std::atoi(argv[++i]);
if (config.port <= 0 || config.port > 65535) {
std::cerr << "Error: Invalid port number. Must be between 1 and 65535"
<< std::endl;
return ParseResult::ERROR;
}
} else if (strcmp(argv[i], "--mount") == 0) {
if (i + 1 >= argc) {
std::cerr
<< "Error: --mount requires mount_point:document_root argument"
<< std::endl;
print_usage(argv[0]);
return ParseResult::ERROR;
}
std::string mount_arg = argv[++i];
auto colon_pos = mount_arg.find(':');
if (colon_pos == std::string::npos) {
std::cerr << "Error: --mount argument must be in format "
"mount_point:document_root"
<< std::endl;
print_usage(argv[0]);
return ParseResult::ERROR;
}
config.mount_point = mount_arg.substr(0, colon_pos);
config.document_root = mount_arg.substr(colon_pos + 1);
if (config.mount_point.empty() || config.document_root.empty()) {
std::cerr
<< "Error: Both mount_point and document_root must be non-empty"
<< std::endl;
return ParseResult::ERROR;
}
} else if (strcmp(argv[i], "--version") == 0) {
std::cout << CPPHTTPLIB_VERSION << std::endl;
return ParseResult::VERSION_REQUESTED;
} else {
std::cerr << "Error: Unknown option '" << argv[i] << "'" << std::endl;
print_usage(argv[0]);
return ParseResult::ERROR;
}
}
return ParseResult::SUCCESS;
}
bool setup_server(Server &svr, const ServerConfig &config) {
svr.set_logger(nginx_access_logger);
svr.set_error_logger(nginx_error_logger);
auto ret = svr.set_mount_point(config.mount_point, config.document_root);
if (!ret) {
std::cerr
<< std::format(
"Error: Cannot mount '{}' to '{}'. Directory may not exist.",
config.mount_point, config.document_root)
<< std::endl;
return false;
}
svr.set_file_extension_and_mimetype_mapping("html", "text/html");
svr.set_file_extension_and_mimetype_mapping("htm", "text/html");
svr.set_file_extension_and_mimetype_mapping("css", "text/css");
svr.set_file_extension_and_mimetype_mapping("js", "text/javascript");
svr.set_file_extension_and_mimetype_mapping("json", "application/json");
svr.set_file_extension_and_mimetype_mapping("xml", "application/xml");
svr.set_file_extension_and_mimetype_mapping("png", "image/png");
svr.set_file_extension_and_mimetype_mapping("jpg", "image/jpeg");
svr.set_file_extension_and_mimetype_mapping("jpeg", "image/jpeg");
svr.set_file_extension_and_mimetype_mapping("gif", "image/gif");
svr.set_file_extension_and_mimetype_mapping("svg", "image/svg+xml");
svr.set_file_extension_and_mimetype_mapping("ico", "image/x-icon");
svr.set_file_extension_and_mimetype_mapping("pdf", "application/pdf");
svr.set_file_extension_and_mimetype_mapping("zip", "application/zip");
svr.set_file_extension_and_mimetype_mapping("txt", "text/plain");
svr.set_error_handler([](const Request & /*req*/, Response &res) {
if (res.status == 404) {
res.set_content(
std::format(
"<html><head><title>404 Not Found</title></head>"
"<body><h1>404 Not Found</h1>"
"<p>The requested resource was not found on this server.</p>"
"<hr><p>{}</p></body></html>",
SERVER_NAME),
"text/html");
}
}); });
svr.set_logger( svr.set_pre_routing_handler([](const Request & /*req*/, Response &res) {
[](auto &req, auto &res) { std::cout << log(req, res) << std::endl; }); res.set_header("Server", SERVER_NAME);
return Server::HandlerResponse::Unhandled;
});
svr.set_mount_point("/", base_dir); signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
std::cout << std::format("Serving HTTP on {0} port {1} ...", host, port) return true;
}
int main(int argc, char *argv[]) {
ServerConfig config;
auto result = parse_command_line(argc, argv, config);
switch (result) {
case ParseResult::HELP_REQUESTED:
case ParseResult::VERSION_REQUESTED: return 0;
case ParseResult::ERROR: return 1;
case ParseResult::SUCCESS: break;
}
if (!setup_server(svr, config)) { return 1; }
std::cout << "Serving HTTP on " << config.hostname << ":" << config.port
<< std::endl; << std::endl;
std::cout << "Mount point: " << config.mount_point << " -> "
<< config.document_root << std::endl;
std::cout << "Press Ctrl+C to shutdown gracefully..." << std::endl;
auto ret = svr.listen(host, port); auto ret = svr.listen(config.hostname, config.port);
std::cout << "Server has been shut down." << std::endl;
return ret ? 0 : 1; return ret ? 0 : 1;
} }
+286 -82
View File
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -39,12 +39,12 @@ endif
deps = [dependency('threads')] deps = [dependency('threads')]
args = [] args = []
openssl_dep = dependency('openssl', version: '>=3.0.0', required: get_option('cpp-httplib_openssl')) openssl_dep = dependency('openssl', version: '>=3.0.0', required: get_option('openssl'))
if openssl_dep.found() if openssl_dep.found()
deps += openssl_dep deps += openssl_dep
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT' args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
if host_machine.system() == 'darwin' if host_machine.system() == 'darwin'
macosx_keychain_dep = dependency('appleframeworks', modules: ['CoreFoundation', 'Security'], required: get_option('cpp-httplib_macosx_keychain')) macosx_keychain_dep = dependency('appleframeworks', modules: ['CoreFoundation', 'Security'], required: get_option('macosx_keychain'))
if macosx_keychain_dep.found() if macosx_keychain_dep.found()
deps += macosx_keychain_dep deps += macosx_keychain_dep
args += '-DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN' args += '-DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN'
@@ -52,15 +52,15 @@ if openssl_dep.found()
endif endif
endif endif
zlib_dep = dependency('zlib', required: get_option('cpp-httplib_zlib')) zlib_dep = dependency('zlib', required: get_option('zlib'))
if zlib_dep.found() if zlib_dep.found()
deps += zlib_dep deps += zlib_dep
args += '-DCPPHTTPLIB_ZLIB_SUPPORT' args += '-DCPPHTTPLIB_ZLIB_SUPPORT'
endif endif
brotli_deps = [dependency('libbrotlicommon', required: get_option('cpp-httplib_brotli'))] brotli_deps = [dependency('libbrotlicommon', required: get_option('brotli'))]
brotli_deps += dependency('libbrotlidec', required: get_option('cpp-httplib_brotli')) brotli_deps += dependency('libbrotlidec', required: get_option('brotli'))
brotli_deps += dependency('libbrotlienc', required: get_option('cpp-httplib_brotli')) brotli_deps += dependency('libbrotlienc', required: get_option('brotli'))
brotli_found_all = true brotli_found_all = true
foreach brotli_dep : brotli_deps foreach brotli_dep : brotli_deps
@@ -74,7 +74,7 @@ if brotli_found_all
args += '-DCPPHTTPLIB_BROTLI_SUPPORT' args += '-DCPPHTTPLIB_BROTLI_SUPPORT'
endif endif
async_ns_opt = get_option('cpp-httplib_non_blocking_getaddrinfo') async_ns_opt = get_option('non_blocking_getaddrinfo')
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
async_ns_dep = cxx.find_library('ws2_32', required: async_ns_opt) async_ns_dep = cxx.find_library('ws2_32', required: async_ns_opt)
@@ -91,7 +91,7 @@ endif
cpp_httplib_dep = dependency('', required: false) cpp_httplib_dep = dependency('', required: false)
if get_option('cpp-httplib_compile') if get_option('compile')
python3 = find_program('python3') python3 = find_program('python3')
httplib_ch = custom_target( httplib_ch = custom_target(
@@ -135,6 +135,6 @@ endif
meson.override_dependency('cpp-httplib', cpp_httplib_dep) meson.override_dependency('cpp-httplib', cpp_httplib_dep)
if get_option('cpp-httplib_test') if get_option('test')
subdir('test') subdir('test')
endif endif
+89
View File
@@ -3200,6 +3200,11 @@ protected:
[&](const Request & /*req*/, Response &res) { [&](const Request & /*req*/, Response &res) {
res.set_content("abcdefg", "text/plain"); res.set_content("abcdefg", "text/plain");
}) })
.Get("/test-start-time",
[&](const Request &req, Response & /*res*/) {
EXPECT_NE(req.start_time_,
std::chrono::steady_clock::time_point::min());
})
.Get("/with-range-customized-response", .Get("/with-range-customized-response",
[&](const Request & /*req*/, Response &res) { [&](const Request & /*req*/, Response &res) {
res.status = StatusCode::BadRequest_400; res.status = StatusCode::BadRequest_400;
@@ -5800,6 +5805,8 @@ TEST_F(ServerTest, TooManyRedirect) {
EXPECT_EQ(Error::ExceedRedirectCount, res.error()); EXPECT_EQ(Error::ExceedRedirectCount, res.error());
} }
TEST_F(ServerTest, StartTime) { auto res = cli_.Get("/test-start-time"); }
#ifdef CPPHTTPLIB_ZLIB_SUPPORT #ifdef CPPHTTPLIB_ZLIB_SUPPORT
TEST_F(ServerTest, Gzip) { TEST_F(ServerTest, Gzip) {
Headers headers; Headers headers;
@@ -7359,6 +7366,48 @@ TEST(KeepAliveTest, SSLClientReconnectionPost) {
ASSERT_TRUE(result); ASSERT_TRUE(result);
EXPECT_EQ(200, result->status); EXPECT_EQ(200, result->status);
} }
TEST(SNI_AutoDetectionTest, SNI_Logic) {
{
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
svr.Get("/sni", [&](const Request &req, Response &res) {
std::string expected;
if (req.ssl) {
if (const char *sni =
SSL_get_servername(req.ssl, TLSEXT_NAMETYPE_host_name)) {
expected = sni;
}
}
EXPECT_EQ(expected, req.get_param_value("expected"));
res.set_content("ok", "text/plain");
});
auto listen_thread = std::thread([&svr] { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
{
SSLClient cli("localhost", PORT);
cli.enable_server_certificate_verification(false);
auto res = cli.Get("/sni?expected=localhost");
ASSERT_TRUE(res);
}
{
SSLClient cli("::1", PORT);
cli.enable_server_certificate_verification(false);
auto res = cli.Get("/sni?expected=");
ASSERT_TRUE(res);
}
}
}
#endif #endif
TEST(ClientProblemDetectionTest, ContentProvider) { TEST(ClientProblemDetectionTest, ContentProvider) {
@@ -8963,6 +9012,46 @@ TEST(HttpToHttpsRedirectTest, CertFile) {
ASSERT_EQ(StatusCode::OK_200, res->status); ASSERT_EQ(StatusCode::OK_200, res->status);
} }
TEST(SSLClientRedirectTest, CertFile) {
SSLServer ssl_svr1(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(ssl_svr1.is_valid());
ssl_svr1.Get("/index", [&](const Request &, Response &res) {
res.set_redirect("https://127.0.0.1:1235/index");
ssl_svr1.stop();
});
SSLServer ssl_svr2(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(ssl_svr2.is_valid());
ssl_svr2.Get("/index", [&](const Request &, Response &res) {
res.set_content("test", "text/plain");
ssl_svr2.stop();
});
thread t = thread([&]() { ASSERT_TRUE(ssl_svr1.listen("127.0.0.1", PORT)); });
thread t2 =
thread([&]() { ASSERT_TRUE(ssl_svr2.listen("127.0.0.1", 1235)); });
auto se = detail::scope_exit([&] {
t2.join();
t.join();
ASSERT_FALSE(ssl_svr1.is_running());
});
ssl_svr1.wait_until_ready();
ssl_svr2.wait_until_ready();
SSLClient cli("127.0.0.1", PORT);
std::string cert;
read_file(SERVER_CERT2_FILE, cert);
cli.load_ca_cert_store(cert.c_str(), cert.size());
cli.enable_server_certificate_verification(true);
cli.set_follow_location(true);
cli.set_connection_timeout(30);
auto res = cli.Get("/index");
ASSERT_TRUE(res);
ASSERT_EQ(StatusCode::OK_200, res->status);
}
TEST(MultipartFormDataTest, LargeData) { TEST(MultipartFormDataTest, LargeData) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE); SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);