#ifndef SIMPLE_HTTPSERVER_HPP #define SIMPLE_HTTPSERVER_HPP #include #include #include #include #include #include #include #include #include #include #include "timing_decorator.hpp" #ifdef _WIN32 #include #include #pragma comment(lib, "ws2_32.lib") #else #include #include #include #include #endif class SimpleHTTPServer { private: int serverSocket; int port; bool running; std::string webRoot; // Route handler type using RouteHandler = std::function(const std::string&, const std::string&)>; std::unordered_map routes; // Read file content std::string readFile(const std::string& filename) { std::ifstream file(filename, std::ios::binary); if (!file) { return ""; } std::ostringstream ss; ss << file.rdbuf(); return ss.str(); } // Get content type based on file extension std::string getContentType(const std::string& filename) { if (filename.find(".html") != std::string::npos) return "text/html"; if (filename.find(".css") != std::string::npos) return "text/css"; if (filename.find(".js") != std::string::npos) return "application/javascript"; if (filename.find(".jxl") != std::string::npos) return "image/jxl"; if (filename.find(".png") != std::string::npos) return "image/png"; if (filename.find(".jpg") != std::string::npos || filename.find(".jpeg") != std::string::npos) return "image/jpeg"; if (filename.find(".json") != std::string::npos) return "application/json"; if (filename.find(".ico") != std::string::npos) return "image/x-icon"; return "text/plain"; } // Send HTTP response void sendResponse(int clientSocket, const std::string& content, const std::string& contentType = "text/html", int statusCode = 200) { TIME_FUNCTION; std::string statusText = "OK"; if (statusCode == 404) statusText = "Not Found"; if (statusCode == 500) statusText = "Internal Server Error"; if (statusCode == 405) statusText = "Method Not Allowed"; std::ostringstream response; response << "HTTP/1.1 " << statusCode << " " << statusText << "\r\n"; response << "Content-Type: " << contentType << "\r\n"; response << "Content-Length: " << content.length() << "\r\n"; response << "Access-Control-Allow-Origin: *\r\n"; response << "Connection: close\r\n"; response << "\r\n"; response << content; std::string responseStr = response.str(); send(clientSocket, responseStr.c_str(), responseStr.length(), 0); } // Extract method and path from HTTP request std::pair parseRequest(const std::string& request) { TIME_FUNCTION; std::istringstream iss(request); std::string method, path; iss >> method >> path; return {method, path}; } // Extract file path from HTTP request std::string getFilePath(const std::string& request) { auto [method, path] = parseRequest(request); // Only handle GET requests for files if (method != "GET") { return ""; } // Default to index.html for root path if (path == "/") { return "index.html"; } // Remove leading slash if (path.length() > 0 && path[0] == '/') { path = path.substr(1); } return path; } // Check if path is a registered route bool isRoute(const std::string& path) { return routes.find(path) != routes.end(); } // Handle route request void handleRoute(int clientSocket, const std::string& request) { TIME_FUNCTION; auto [method, path] = parseRequest(request); // Extract request body if present std::string body; size_t bodyPos = request.find("\r\n\r\n"); if (bodyPos != std::string::npos) { body = request.substr(bodyPos + 4); } if (routes.find(path) != routes.end()) { auto [statusCode, response] = routes[path](method, body); sendResponse(clientSocket, response, "application/json", statusCode); } else { sendResponse(clientSocket, "404 Not Found", "text/plain", 404); } } public: SimpleHTTPServer(int port = 8080, const std::string& webRoot = "web") : port(port), serverSocket(-1), running(false), webRoot(webRoot) {} ~SimpleHTTPServer() { stop(); } // Add a route handler void addRoute(const std::string& path, RouteHandler handler) { routes[path] = handler; } bool start() { #ifdef _WIN32 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { std::cerr << "WSAStartup failed" << std::endl; return false; } #endif serverSocket = socket(AF_INET, SOCK_STREAM, 0); if (serverSocket < 0) { std::cerr << "Socket creation failed" << std::endl; return false; } int opt = 1; #ifdef _WIN32 if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) < 0) { #else if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { #endif std::cerr << "Setsockopt failed" << std::endl; return false; } sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(port); if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { std::cerr << "Bind failed on port " << port << std::endl; return false; } if (listen(serverSocket, 10) < 0) { std::cerr << "Listen failed" << std::endl; return false; } running = true; std::cout << "Server started on port " << port << std::endl; std::cout << "Web root: " << webRoot << std::endl; return true; } void stop() { running = false; if (serverSocket >= 0) { #ifdef _WIN32 closesocket(serverSocket); WSACleanup(); #else close(serverSocket); #endif serverSocket = -1; } } void handleRequests() { while (running) { sockaddr_in clientAddr; #ifdef _WIN32 int clientAddrLen = sizeof(clientAddr); #else socklen_t clientAddrLen = sizeof(clientAddr); #endif int clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrLen); if (clientSocket < 0) { if (running) { std::cerr << "Accept failed" << std::endl; } continue; } char buffer[4096] = {0}; int bytesReceived = recv(clientSocket, buffer, sizeof(buffer), 0); if (bytesReceived > 0) { std::string request(buffer); auto [method, path] = parseRequest(request); // Check if this is a registered route if (isRoute(path)) { handleRoute(clientSocket, request); } else { // Handle file serving for GET requests std::string filePath = getFilePath(request); if (!filePath.empty()) { std::cout << "Serving: " << filePath << std::endl; std::string fullPath = webRoot + "/" + filePath; std::string content = readFile(fullPath); if (!content.empty()) { sendResponse(clientSocket, content, getContentType(filePath)); } else { sendResponse(clientSocket, "404 Not Found: " + filePath, "text/plain", 404); } } else { sendResponse(clientSocket, "400 Bad Request", "text/plain", 400); } } } #ifdef _WIN32 closesocket(clientSocket); #else close(clientSocket); #endif std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } }; #endif