Skip to content

Bring select() back on macOS (Fix #2111) #2178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 68 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,23 +269,44 @@ svr.set_file_request_handler([](const Request &req, Response &res) {

### 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
svr.set_logger([](const auto& req, const auto& res) {
your_logger(req, res);
svr.set_access_logger([](const httplib::Request& req, const httplib::Response& 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
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
// Content-Encoding header is not yet set
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 Request and Error information, as errors typically occur before a meaningful Response can be generated.

```cpp
svr.set_error_logger([](const httplib::Request& req, const httplib::Error& err) {
std::cerr << httplib::to_string(err) << " while processing request"
<< ", client: " << req.get_header_value("X-Forwarded-For")
<< ", request: '" << req.method << " " << req.path << " " << req.version << "'"
<< ", host: " << req.get_header_value("Host") << std::endl;
});
```

### Error handler

Expand Down Expand Up @@ -718,6 +739,48 @@ enum Error {
};
```

### Client Logging

#### Access Logging

```cpp
cli.set_access_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::Request& req, const httplib::Error& err) {
std::cerr << "✗ " << req.method << " " << req.path
<< " 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

```c++
Expand Down
7 changes: 5 additions & 2 deletions example/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
BROTLI_DIR = $(PREFIX)/opt/brotli
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec

all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client accept_header
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client accept_header nginxish_server

server : server.cc ../httplib.h Makefile
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
Expand Down Expand Up @@ -59,9 +59,12 @@ server_and_client : server_and_client.cc ../httplib.h Makefile
accept_header : accept_header.cc ../httplib.h Makefile
$(CXX) -o accept_header $(CXXFLAGS) accept_header.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)

nginxish_server : nginxish_server.cc ../httplib.h Makefile
$(CXX) -o nginxish_server $(CXXFLAGS) nginxish_server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)

pem:
openssl genrsa 2048 > key.pem
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem

clean:
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client accept_header *.pem
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client accept_header nginxish_server *.pem
174 changes: 174 additions & 0 deletions example/nginxish_server.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
//
// nginxish_server.cc
//
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License
//

#include <chrono>
#include <ctime>
#include <httplib.h>
#include <iomanip>
#include <iostream>
#include <sstream>

using namespace httplib;

std::string get_nginx_time_format() {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);

std::stringstream ss;
ss << std::put_time(std::localtime(&time_t), "%d/%b/%Y:%H:%M:%S %z");
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) {
std::string remote_addr = get_client_ip(req);
std::string remote_user =
"-"; // cpp-httplib doesn't have built-in auth user tracking
std::string time_local = get_nginx_time_format();
std::string request = req.method + " " + req.path + " HTTP/" + req.version;
int status = res.status;
size_t body_bytes_sent = res.body.size();
std::string http_referer = req.get_header_value("Referer");
if (http_referer.empty()) http_referer = "-";
std::string http_user_agent = req.get_header_value("User-Agent");
if (http_user_agent.empty()) http_user_agent = "-";

std::cout << remote_addr << " - " << remote_user << " [" << time_local << "] "
<< "\"" << request << "\" " << status << " " << body_bytes_sent
<< " "
<< "\"" << http_referer << "\" \"" << http_user_agent << "\""
<< std::endl;
}

// NGINX Error log format:
// [time] [level] pid#tid: *cid message, client: client_ip, server: server_name,
// request: "request", host: "host"
void nginx_error_logger(const Request &req, const Error &err) {
std::string time_local = get_nginx_time_format();
std::string level = "error";
std::string client_ip = get_client_ip(req);
std::string server_name = req.get_header_value("Host");
if (server_name.empty()) server_name = "-";
std::string request = req.method + " " + req.path + " HTTP/" + req.version;
std::string host = req.get_header_value("Host");
if (host.empty()) host = "-";

std::cerr << "[" << time_local << "] [" << level << "] " << to_string(err)
<< ", client: " << client_ip << ", server: " << server_name
<< ", request: \"" << request << "\""
<< ", host: \"" << host << "\"" << std::endl;
}

void print_usage(const char *program_name) {
std::cerr << "Usage: " << program_name
<< " <hostname> <port> <mount_point> <document_root_directory>"
<< std::endl;
std::cerr << "Example: " << program_name << " localhost 8080 /var/www/html ."
<< std::endl;
}

int main(int argc, char *argv[]) {
if (argc != 5) {
print_usage(argv[0]);
return 1;
}

std::string hostname = argv[1];
int port = std::atoi(argv[2]);
std::string mount_point = argv[3];
std::string document_root = argv[4];

if (port <= 0 || port > 65535) {
std::cerr << "Error: Invalid port number. Must be between 1 and 65535."
<< std::endl;
return 1;
}

Server svr;

// Set up NGINX-style logging
svr.set_access_logger(nginx_access_logger);
svr.set_error_logger(nginx_error_logger);

// Set up static file serving
auto ret = svr.set_mount_point(mount_point, document_root);
if (!ret) {
std::cerr << "Error: Cannot mount '" << mount_point << "' to '"
<< document_root << "'. Directory may not exist." << std::endl;
return 1;
}

// Add some common MIME types (similar to NGINX)
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");

// Custom error handler for 404s
svr.set_error_handler([](const Request & /*req*/, Response &res) {
if (res.status == 404) {
res.set_content(
"<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>nginxish_server/1.0</p></body></html>",
"text/html");
}
});

// Set server header to mimic NGINX
svr.set_pre_routing_handler([](const Request & /*req*/, Response &res) {
res.set_header("Server", "nginxish_server/1.0");
return Server::HandlerResponse::Unhandled;
});

std::cout << "Starting nginxish_server on " << hostname << ":" << port
<< std::endl;
std::cout << "Document root: " << document_root << std::endl;
std::cout << "Mount point: " << mount_point << " -> " << document_root
<< std::endl;
std::cout << "Press Ctrl+C to stop the server" << std::endl;

// Start the server
svr.listen(hostname, port);

return 0;
}
2 changes: 1 addition & 1 deletion example/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ int main(void) {
res.set_content(buf, "text/html");
});

svr.set_logger([](const Request &req, const Response &res) {
svr.set_access_logger([](const Request &req, const Response &res) {
printf("%s", log(req, res).c_str());
});

Expand Down
2 changes: 1 addition & 1 deletion example/server_and_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ void logger(const Request &req, const Response &res) {
int main(void) {
// Server
Server svr;
svr.set_logger(logger);
svr.set_access_logger(logger);

svr.Post("/post", [&](const Request & /*req*/, Response &res) {
res.set_content("POST", "text/plain");
Expand Down
2 changes: 1 addition & 1 deletion example/simplesvr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ int main(int argc, const char **argv) {
res.set_content(buf, "text/html");
});

svr.set_logger(
svr.set_access_logger(
[](const Request &req, const Response &res) { cout << log(req, res); });

auto port = 8080;
Expand Down
Loading
Loading