Skip to content

Commit 06590b2

Browse files
committed
ErrorLogger support (#870)
1 parent acf28a3 commit 06590b2

File tree

3 files changed

+338
-63
lines changed

3 files changed

+338
-63
lines changed

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ FROM scratch
88
COPY --from=builder /build/server /server
99
COPY docker/html/index.html /html/index.html
1010
EXPOSE 80
11-
CMD ["/server"]
11+
12+
ENTRYPOINT ["/server"]
13+
CMD ["0.0.0.0", "80", "/", "/html"]

docker/main.cc

Lines changed: 168 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,77 +5,202 @@
55
// MIT License
66
//
77

8+
#include <atomic>
89
#include <chrono>
910
#include <ctime>
1011
#include <format>
1112
#include <iomanip>
1213
#include <iostream>
14+
#include <signal.h>
1315
#include <sstream>
1416

1517
#include <httplib.h>
1618

17-
constexpr auto error_html = R"(<html>
18-
<head><title>{} {}</title></head>
19-
<body>
20-
<center><h1>404 Not Found</h1></center>
21-
<hr><center>cpp-httplib/{}</center>
22-
</body>
23-
</html>
24-
)";
19+
using namespace httplib;
2520

26-
void sigint_handler(int s) { exit(1); }
21+
auto SERVER_NAME = std::format("nginxish_server/{}", CPPHTTPLIB_VERSION);
2722

28-
std::string time_local() {
29-
auto p = std::chrono::system_clock::now();
30-
auto t = std::chrono::system_clock::to_time_t(p);
23+
Server svr;
24+
25+
void signal_handler(int signal) {
26+
if (signal == SIGINT || signal == SIGTERM) {
27+
std::cout << std::format("\nReceived signal, shutting down gracefully...")
28+
<< std::endl;
29+
svr.stop();
30+
}
31+
}
32+
33+
std::string get_nginx_time_format() {
34+
auto now = std::chrono::system_clock::now();
35+
auto time_t = std::chrono::system_clock::to_time_t(now);
3136

3237
std::stringstream ss;
33-
ss << std::put_time(std::localtime(&t), "%d/%b/%Y:%H:%M:%S %z");
38+
ss << std::put_time(std::localtime(&time_t), "%d/%b/%Y:%H:%M:%S %z");
3439
return ss.str();
3540
}
3641

37-
std::string log(auto &req, auto &res) {
38-
auto remote_user = "-"; // TODO:
39-
auto request = std::format("{} {} {}", req.method, req.path, req.version);
40-
auto body_bytes_sent = res.get_header_value("Content-Length");
41-
auto http_referer = "-"; // TODO:
42-
auto http_user_agent = req.get_header_value("User-Agent", "-");
43-
44-
// NOTE: From NGINX default access log format
45-
// log_format combined '$remote_addr - $remote_user [$time_local] '
46-
// '"$request" $status $body_bytes_sent '
47-
// '"$http_referer" "$http_user_agent"';
48-
return std::format(R"({} - {} [{}] "{}" {} {} "{}" "{}")", req.remote_addr,
49-
remote_user, time_local(), request, res.status,
50-
body_bytes_sent, http_referer, http_user_agent);
42+
std::string get_nginx_error_time_format() {
43+
auto now = std::chrono::system_clock::now();
44+
auto time_t = std::chrono::system_clock::to_time_t(now);
45+
46+
std::stringstream ss;
47+
ss << std::put_time(std::localtime(&time_t), "%Y/%m/%d %H:%M:%S");
48+
return ss.str();
49+
}
50+
51+
std::string get_client_ip(const Request &req) {
52+
// Check for X-Forwarded-For header first (common in reverse proxy setups)
53+
auto forwarded_for = req.get_header_value("X-Forwarded-For");
54+
if (!forwarded_for.empty()) {
55+
// Get the first IP if there are multiple
56+
auto comma_pos = forwarded_for.find(',');
57+
if (comma_pos != std::string::npos) {
58+
return forwarded_for.substr(0, comma_pos);
59+
}
60+
return forwarded_for;
61+
}
62+
63+
// Check for X-Real-IP header
64+
auto real_ip = req.get_header_value("X-Real-IP");
65+
if (!real_ip.empty()) { return real_ip; }
66+
67+
// Fallback to remote address (though cpp-httplib doesn't provide this
68+
// directly) For demonstration, we'll use a placeholder
69+
return "127.0.0.1";
5170
}
5271

53-
int main(int argc, const char **argv) {
54-
signal(SIGINT, sigint_handler);
72+
// NGINX Combined log format:
73+
// $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent
74+
// "$http_referer" "$http_user_agent"
75+
void nginx_access_logger(const Request &req, const Response &res) {
76+
std::string remote_addr = get_client_ip(req);
77+
std::string remote_user =
78+
"-"; // cpp-httplib doesn't have built-in auth user tracking
79+
std::string time_local = get_nginx_time_format();
80+
std::string request =
81+
std::format("{} {} {}", req.method, req.path, req.version);
82+
int status = res.status;
83+
size_t body_bytes_sent = res.body.size();
84+
std::string http_referer = req.get_header_value("Referer");
85+
if (http_referer.empty()) http_referer = "-";
86+
std::string http_user_agent = req.get_header_value("User-Agent");
87+
if (http_user_agent.empty()) http_user_agent = "-";
88+
89+
std::cout << std::format("{} - {} [{}] \"{}\" {} {} \"{}\" \"{}", remote_addr,
90+
remote_user, time_local, request, status,
91+
body_bytes_sent, http_referer, http_user_agent)
92+
<< std::endl;
93+
}
5594

56-
auto base_dir = "./html";
57-
auto host = "0.0.0.0";
58-
auto port = 80;
95+
// NGINX Error log format:
96+
// YYYY/MM/DD HH:MM:SS [level] PID#TID: *CID message, client: client_ip, server:
97+
// server_name, request: "request", host: "host"
98+
void nginx_error_logger(const Error &err, const Request *req) {
99+
std::string time_local = get_nginx_error_time_format();
100+
std::string level = "error";
101+
if (req) {
102+
std::string client_ip = get_client_ip(*req);
103+
std::string server_name = req->get_header_value("Host");
104+
if (server_name.empty()) server_name = "-";
105+
std::string request =
106+
std::format("{} {} {}", req->method, req->path, req->version);
107+
std::string host = req->get_header_value("Host");
108+
if (host.empty()) host = "-";
109+
110+
std::cerr << std::format("{} [{}] {}, client: {}, server: {}, request: "
111+
"\"{}\", host: \"{}\"",
112+
time_local, level, to_string(err), client_ip,
113+
server_name, request, host)
114+
<< std::endl;
115+
} else {
116+
// If no request context, just log the error
117+
std::cerr << std::format("{} [{}] {}", time_local, level, to_string(err))
118+
<< std::endl;
119+
}
120+
}
59121

60-
httplib::Server svr;
122+
void print_usage(const char *program_name) {
123+
std::cout << std::format("Usage: {} <hostname> <port> <mount_point> "
124+
"<document_root_directory>",
125+
program_name)
126+
<< std::endl;
61127

62-
svr.set_error_handler([](auto & /*req*/, auto &res) {
63-
auto body =
64-
std::format(error_html, res.status, httplib::status_message(res.status),
65-
CPPHTTPLIB_VERSION);
128+
std::cout << std::format("Example: {} localhost 8080 /var/www/html .",
129+
program_name)
130+
<< std::endl;
131+
}
66132

67-
res.set_content(body, "text/html");
133+
int main(int argc, char *argv[]) {
134+
if (argc != 5) {
135+
print_usage(argv[0]);
136+
return 1;
137+
}
138+
139+
std::string hostname = argv[1];
140+
auto port = std::atoi(argv[2]);
141+
std::string mount_point = argv[3];
142+
std::string document_root = argv[4];
143+
144+
svr.set_logger(nginx_access_logger);
145+
svr.set_error_logger(nginx_error_logger);
146+
147+
auto ret = svr.set_mount_point(mount_point, document_root);
148+
if (!ret) {
149+
std::cerr
150+
<< std::format(
151+
"Error: Cannot mount '{}' to '{}'. Directory may not exist.",
152+
mount_point, document_root)
153+
<< std::endl;
154+
return 1;
155+
}
156+
157+
svr.set_file_extension_and_mimetype_mapping("html", "text/html");
158+
svr.set_file_extension_and_mimetype_mapping("htm", "text/html");
159+
svr.set_file_extension_and_mimetype_mapping("css", "text/css");
160+
svr.set_file_extension_and_mimetype_mapping("js", "text/javascript");
161+
svr.set_file_extension_and_mimetype_mapping("json", "application/json");
162+
svr.set_file_extension_and_mimetype_mapping("xml", "application/xml");
163+
svr.set_file_extension_and_mimetype_mapping("png", "image/png");
164+
svr.set_file_extension_and_mimetype_mapping("jpg", "image/jpeg");
165+
svr.set_file_extension_and_mimetype_mapping("jpeg", "image/jpeg");
166+
svr.set_file_extension_and_mimetype_mapping("gif", "image/gif");
167+
svr.set_file_extension_and_mimetype_mapping("svg", "image/svg+xml");
168+
svr.set_file_extension_and_mimetype_mapping("ico", "image/x-icon");
169+
svr.set_file_extension_and_mimetype_mapping("pdf", "application/pdf");
170+
svr.set_file_extension_and_mimetype_mapping("zip", "application/zip");
171+
svr.set_file_extension_and_mimetype_mapping("txt", "text/plain");
172+
173+
svr.set_error_handler([](const Request & /*req*/, Response &res) {
174+
if (res.status == 404) {
175+
res.set_content(
176+
std::format(
177+
"<html><head><title>404 Not Found</title></head>"
178+
"<body><h1>404 Not Found</h1>"
179+
"<p>The requested resource was not found on this server.</p>"
180+
"<hr><p>{}</p></body></html>",
181+
SERVER_NAME),
182+
"text/html");
183+
}
68184
});
69185

70-
svr.set_logger(
71-
[](auto &req, auto &res) { std::cout << log(req, res) << std::endl; });
186+
svr.set_pre_routing_handler([](const Request & /*req*/, Response &res) {
187+
res.set_header("Server", SERVER_NAME);
188+
return Server::HandlerResponse::Unhandled;
189+
});
72190

73-
svr.set_mount_point("/", base_dir);
191+
signal(SIGINT, signal_handler);
192+
signal(SIGTERM, signal_handler);
74193

75-
std::cout << std::format("Serving HTTP on {0} port {1} ...", host, port)
194+
std::cout << std::format("Serving HTTP on {}:{}", hostname, port)
195+
<< std::endl;
196+
std::cout << std::format("Mount point: {} -> {}", mount_point, document_root)
76197
<< std::endl;
198+
std::cout << std::format("Press Ctrl+C to shutdown gracefully...")
199+
<< std::endl;
200+
201+
ret = svr.listen(hostname, port);
77202

78-
auto ret = svr.listen(host, port);
203+
std::cout << std::format("Server has been shut down.") << std::endl;
79204

80205
return ret ? 0 : 1;
81206
}

0 commit comments

Comments
 (0)