|
5 | 5 | // MIT License
|
6 | 6 | //
|
7 | 7 |
|
| 8 | +#include <atomic> |
8 | 9 | #include <chrono>
|
9 | 10 | #include <ctime>
|
10 | 11 | #include <format>
|
11 | 12 | #include <iomanip>
|
12 | 13 | #include <iostream>
|
| 14 | +#include <signal.h> |
13 | 15 | #include <sstream>
|
14 | 16 |
|
15 | 17 | #include <httplib.h>
|
16 | 18 |
|
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; |
25 | 20 |
|
26 |
| -void sigint_handler(int s) { exit(1); } |
| 21 | +auto SERVER_NAME = |
| 22 | + std::format("cpp-httplib-nginxish-server/{}", CPPHTTPLIB_VERSION); |
27 | 23 |
|
28 |
| -std::string time_local() { |
29 |
| - auto p = std::chrono::system_clock::now(); |
30 |
| - auto t = std::chrono::system_clock::to_time_t(p); |
| 24 | +Server svr; |
| 25 | + |
| 26 | +void signal_handler(int signal) { |
| 27 | + if (signal == SIGINT || signal == SIGTERM) { |
| 28 | + std::cout << std::format("\nReceived signal, shutting down gracefully...") |
| 29 | + << std::endl; |
| 30 | + svr.stop(); |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +std::string get_nginx_time_format() { |
| 35 | + auto now = std::chrono::system_clock::now(); |
| 36 | + auto time_t = std::chrono::system_clock::to_time_t(now); |
31 | 37 |
|
32 | 38 | std::stringstream ss;
|
33 |
| - ss << std::put_time(std::localtime(&t), "%d/%b/%Y:%H:%M:%S %z"); |
| 39 | + ss << std::put_time(std::localtime(&time_t), "%d/%b/%Y:%H:%M:%S %z"); |
34 | 40 | return ss.str();
|
35 | 41 | }
|
36 | 42 |
|
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); |
| 43 | +std::string get_nginx_error_time_format() { |
| 44 | + auto now = std::chrono::system_clock::now(); |
| 45 | + auto time_t = std::chrono::system_clock::to_time_t(now); |
| 46 | + |
| 47 | + std::stringstream ss; |
| 48 | + ss << std::put_time(std::localtime(&time_t), "%Y/%m/%d %H:%M:%S"); |
| 49 | + return ss.str(); |
| 50 | +} |
| 51 | + |
| 52 | +std::string get_client_ip(const Request &req) { |
| 53 | + // Check for X-Forwarded-For header first (common in reverse proxy setups) |
| 54 | + auto forwarded_for = req.get_header_value("X-Forwarded-For"); |
| 55 | + if (!forwarded_for.empty()) { |
| 56 | + // Get the first IP if there are multiple |
| 57 | + auto comma_pos = forwarded_for.find(','); |
| 58 | + if (comma_pos != std::string::npos) { |
| 59 | + return forwarded_for.substr(0, comma_pos); |
| 60 | + } |
| 61 | + return forwarded_for; |
| 62 | + } |
| 63 | + |
| 64 | + // Check for X-Real-IP header |
| 65 | + auto real_ip = req.get_header_value("X-Real-IP"); |
| 66 | + if (!real_ip.empty()) { return real_ip; } |
| 67 | + |
| 68 | + // Fallback to remote address (though cpp-httplib doesn't provide this |
| 69 | + // directly) For demonstration, we'll use a placeholder |
| 70 | + return "127.0.0.1"; |
51 | 71 | }
|
52 | 72 |
|
53 |
| -int main(int argc, const char **argv) { |
54 |
| - signal(SIGINT, sigint_handler); |
| 73 | +// NGINX Combined log format: |
| 74 | +// $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent |
| 75 | +// "$http_referer" "$http_user_agent" |
| 76 | +void nginx_access_logger(const Request &req, const Response &res) { |
| 77 | + std::string remote_addr = get_client_ip(req); |
| 78 | + std::string remote_user = |
| 79 | + "-"; // cpp-httplib doesn't have built-in auth user tracking |
| 80 | + std::string time_local = get_nginx_time_format(); |
| 81 | + std::string request = |
| 82 | + std::format("{} {} {}", req.method, req.path, req.version); |
| 83 | + int status = res.status; |
| 84 | + size_t body_bytes_sent = res.body.size(); |
| 85 | + std::string http_referer = req.get_header_value("Referer"); |
| 86 | + if (http_referer.empty()) http_referer = "-"; |
| 87 | + std::string http_user_agent = req.get_header_value("User-Agent"); |
| 88 | + if (http_user_agent.empty()) http_user_agent = "-"; |
| 89 | + |
| 90 | + std::cout << std::format("{} - {} [{}] \"{}\" {} {} \"{}\" \"{}\"", |
| 91 | + remote_addr, remote_user, time_local, request, |
| 92 | + status, body_bytes_sent, http_referer, |
| 93 | + http_user_agent) |
| 94 | + << std::endl; |
| 95 | +} |
55 | 96 |
|
56 |
| - auto base_dir = "./html"; |
57 |
| - auto host = "0.0.0.0"; |
58 |
| - auto port = 80; |
| 97 | +// NGINX Error log format: |
| 98 | +// YYYY/MM/DD HH:MM:SS [level] message, client: client_ip, request: "request", |
| 99 | +// host: "host" |
| 100 | +void nginx_error_logger(const Error &err, const Request *req) { |
| 101 | + std::string time_local = get_nginx_error_time_format(); |
| 102 | + std::string level = "error"; |
| 103 | + |
| 104 | + if (req) { |
| 105 | + std::string client_ip = get_client_ip(*req); |
| 106 | + std::string request = |
| 107 | + std::format("{} {} {}", req->method, req->path, req->version); |
| 108 | + std::string host = req->get_header_value("Host"); |
| 109 | + if (host.empty()) host = "-"; |
| 110 | + |
| 111 | + std::cerr << std::format("{} [{}] {}, client: {}, request: " |
| 112 | + "\"{}\", host: \"{}\"", |
| 113 | + time_local, level, to_string(err), client_ip, |
| 114 | + request, host) |
| 115 | + << std::endl; |
| 116 | + } else { |
| 117 | + // If no request context, just log the error |
| 118 | + std::cerr << std::format("{} [{}] {}", time_local, level, to_string(err)) |
| 119 | + << std::endl; |
| 120 | + } |
| 121 | +} |
59 | 122 |
|
60 |
| - httplib::Server svr; |
| 123 | +void print_usage(const char *program_name) { |
| 124 | + std::cout << std::format("Usage: {} <hostname> <port> <mount_point> " |
| 125 | + "<document_root_directory>", |
| 126 | + program_name) |
| 127 | + << std::endl; |
61 | 128 |
|
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); |
| 129 | + std::cout << std::format("Example: {} localhost 8080 /var/www/html .", |
| 130 | + program_name) |
| 131 | + << std::endl; |
| 132 | +} |
66 | 133 |
|
67 |
| - res.set_content(body, "text/html"); |
| 134 | +int main(int argc, char *argv[]) { |
| 135 | + if (argc != 5) { |
| 136 | + print_usage(argv[0]); |
| 137 | + return 1; |
| 138 | + } |
| 139 | + |
| 140 | + std::string hostname = argv[1]; |
| 141 | + auto port = std::atoi(argv[2]); |
| 142 | + std::string mount_point = argv[3]; |
| 143 | + std::string document_root = argv[4]; |
| 144 | + |
| 145 | + svr.set_logger(nginx_access_logger); |
| 146 | + svr.set_error_logger(nginx_error_logger); |
| 147 | + |
| 148 | + auto ret = svr.set_mount_point(mount_point, document_root); |
| 149 | + if (!ret) { |
| 150 | + std::cerr |
| 151 | + << std::format( |
| 152 | + "Error: Cannot mount '{}' to '{}'. Directory may not exist.", |
| 153 | + mount_point, document_root) |
| 154 | + << std::endl; |
| 155 | + return 1; |
| 156 | + } |
| 157 | + |
| 158 | + svr.set_file_extension_and_mimetype_mapping("html", "text/html"); |
| 159 | + svr.set_file_extension_and_mimetype_mapping("htm", "text/html"); |
| 160 | + svr.set_file_extension_and_mimetype_mapping("css", "text/css"); |
| 161 | + svr.set_file_extension_and_mimetype_mapping("js", "text/javascript"); |
| 162 | + svr.set_file_extension_and_mimetype_mapping("json", "application/json"); |
| 163 | + svr.set_file_extension_and_mimetype_mapping("xml", "application/xml"); |
| 164 | + svr.set_file_extension_and_mimetype_mapping("png", "image/png"); |
| 165 | + svr.set_file_extension_and_mimetype_mapping("jpg", "image/jpeg"); |
| 166 | + svr.set_file_extension_and_mimetype_mapping("jpeg", "image/jpeg"); |
| 167 | + svr.set_file_extension_and_mimetype_mapping("gif", "image/gif"); |
| 168 | + svr.set_file_extension_and_mimetype_mapping("svg", "image/svg+xml"); |
| 169 | + svr.set_file_extension_and_mimetype_mapping("ico", "image/x-icon"); |
| 170 | + svr.set_file_extension_and_mimetype_mapping("pdf", "application/pdf"); |
| 171 | + svr.set_file_extension_and_mimetype_mapping("zip", "application/zip"); |
| 172 | + svr.set_file_extension_and_mimetype_mapping("txt", "text/plain"); |
| 173 | + |
| 174 | + svr.set_error_handler([](const Request & /*req*/, Response &res) { |
| 175 | + if (res.status == 404) { |
| 176 | + res.set_content( |
| 177 | + std::format( |
| 178 | + "<html><head><title>404 Not Found</title></head>" |
| 179 | + "<body><h1>404 Not Found</h1>" |
| 180 | + "<p>The requested resource was not found on this server.</p>" |
| 181 | + "<hr><p>{}</p></body></html>", |
| 182 | + SERVER_NAME), |
| 183 | + "text/html"); |
| 184 | + } |
68 | 185 | });
|
69 | 186 |
|
70 |
| - svr.set_logger( |
71 |
| - [](auto &req, auto &res) { std::cout << log(req, res) << std::endl; }); |
| 187 | + svr.set_pre_routing_handler([](const Request & /*req*/, Response &res) { |
| 188 | + res.set_header("Server", SERVER_NAME); |
| 189 | + return Server::HandlerResponse::Unhandled; |
| 190 | + }); |
72 | 191 |
|
73 |
| - svr.set_mount_point("/", base_dir); |
| 192 | + signal(SIGINT, signal_handler); |
| 193 | + signal(SIGTERM, signal_handler); |
74 | 194 |
|
75 |
| - std::cout << std::format("Serving HTTP on {0} port {1} ...", host, port) |
| 195 | + std::cout << std::format("Serving HTTP on {}:{}", hostname, port) |
| 196 | + << std::endl; |
| 197 | + std::cout << std::format("Mount point: {} -> {}", mount_point, document_root) |
76 | 198 | << std::endl;
|
| 199 | + std::cout << std::format("Press Ctrl+C to shutdown gracefully...") |
| 200 | + << std::endl; |
| 201 | + |
| 202 | + ret = svr.listen(hostname, port); |
77 | 203 |
|
78 |
| - auto ret = svr.listen(host, port); |
| 204 | + std::cout << std::format("Server has been shut down.") << std::endl; |
79 | 205 |
|
80 | 206 | return ret ? 0 : 1;
|
81 | 207 | }
|
0 commit comments