main.cc 6.9 KB

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