From 949c7882594e8c7820904b235950a097c445513a Mon Sep 17 00:00:00 2001 From: whaffman Date: Mon, 29 Sep 2025 17:39:53 +0200 Subject: [PATCH] feat: preliminary error handling with custom error pages and response generation --- webserv/client/Client.cpp | 18 +++---- webserv/config/AConfig.cpp | 41 ++++++++++++---- webserv/config/AConfig.hpp | 3 +- webserv/handler/ErrorHandler.hpp | 22 +++++---- webserv/handler/Errorhandler.cpp | 84 +++++++++++++++++++++++++++----- webserv/http/HttpConstants.hpp | 22 +++++++++ 6 files changed, 146 insertions(+), 44 deletions(-) diff --git a/webserv/client/Client.cpp b/webserv/client/Client.cpp index 3574249..f0d79d6 100644 --- a/webserv/client/Client.cpp +++ b/webserv/client/Client.cpp @@ -1,4 +1,6 @@ #include "webserv/config/ConfigManager.hpp" +#include "webserv/handler/ErrorHandler.hpp" + #include #include // for HttpHeaders #include // for Log, LOCATION @@ -15,8 +17,7 @@ class ServerConfig; Client::Client(std::unique_ptr socket, Server &server) - : client_socket_(std::move(socket)), server_(std::ref(server)), - httpRequest_(std::make_unique(this)) + : client_socket_(std::move(socket)), server_(std::ref(server)), httpRequest_(std::make_unique(this)) { } @@ -73,7 +74,7 @@ void Client::request() Log::warning("No matching server config found for Host: " + httpRequest_->getHeaders().get("Host")); httpRequest_->setState(HttpRequest::State::ParseError); } - // Example usage, replace with actual host and port extraction from request + // Example usage, replace with actual host and port extraction from request server_.responseReady(client_socket_->getFd()); } else @@ -89,16 +90,12 @@ void Client::request() std::string Client::getResponse() const { Log::trace(LOCATION); - std::string response = "HTTP/1.1 "; if (httpRequest_->getState() == HttpRequest::State::ParseError) { - response += "400 Bad Request\r\n"; + return ErrorHandler::generateErrorPage(Http::StatusCode::BAD_REQUEST); } - else - { - response += "200 OK\r\n"; - - // further validation can be added here + std::string response = "HTTP/1.1 "; + response += "200 OK\r\n"; auto serverName = server_config_->getDirectiveValue("server_name"); auto port = server_config_->getDirectiveValue("listen"); @@ -106,7 +103,6 @@ std::string Client::getResponse() const body += "Server port " + std::to_string(port) + "\r\n"; response += "Content-Length: " + std::to_string(body.size()) + "\r\n\r\n"; response += body; - } Log::debug("Sending response:\n" + response); return response; diff --git a/webserv/config/AConfig.cpp b/webserv/config/AConfig.cpp index 07b1b3f..622075e 100644 --- a/webserv/config/AConfig.cpp +++ b/webserv/config/AConfig.cpp @@ -14,16 +14,16 @@ void AConfig::addDirective(const std::string &line) auto directive = DirectiveFactory::createDirective(line); if (directive) { - directives_[directive->getName()] = std::move(directive); + directives_.emplace_back(std::move(directive)); } } const ADirective *AConfig::getDirective(const std::string &name) const { - auto it = directives_.find(name); - if (it != directives_.end()) - { - return it->second.get(); + for (const auto &directive : directives_) { + if (directive->getName() == name) { + return directive.get(); + } } if (parent_ != nullptr) { @@ -34,9 +34,10 @@ const ADirective *AConfig::getDirective(const std::string &name) const bool AConfig::hasDirective(const std::string &name) const { - if (directives_.contains(name)) // NOLINT - { - return true; + for (const auto &directive : directives_) { + if (directive->getName() == name) { + return true; + } } if (parent_ != nullptr) { @@ -59,7 +60,27 @@ void AConfig::parseDirectives(const std::string &declarations) continue; } Log::info("Global Declaration: " + line); - auto directive = DirectiveFactory::createDirective(line); - directives_[directive->getName()] = std::move(directive); + addDirective(line); } } + +std::string AConfig::getErrorPage(int statusCode) const +{ + const ADirective *directive = getDirective("error_page"); + for (const auto &directive : directives_) + { + if (directive->getName() == "error_page") + { + auto value = directive->getValueAs>(); + if (value.first == statusCode) + { + return value.second; + } + } + } + if (parent_ != nullptr) + { + return parent_->getErrorPage(statusCode); + } + return ""; // Return empty string if not found +} \ No newline at end of file diff --git a/webserv/config/AConfig.hpp b/webserv/config/AConfig.hpp index d85a706..dc5720e 100644 --- a/webserv/config/AConfig.hpp +++ b/webserv/config/AConfig.hpp @@ -21,6 +21,7 @@ class AConfig void addDirective(const std::string &line); [[nodiscard]] const ADirective *getDirective(const std::string &name) const; + [[nodiscard]] std::string getErrorPage(int statusCode) const; [[nodiscard]] bool hasDirective(const std::string &name) const; @@ -43,7 +44,7 @@ class AConfig protected: virtual void parseBlock(const std::string &block) = 0; void parseDirectives(const std::string &declarations); - std::map> + std::vector> directives_; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes) const AConfig *parent_ = nullptr; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes) }; \ No newline at end of file diff --git a/webserv/handler/ErrorHandler.hpp b/webserv/handler/ErrorHandler.hpp index b84ecd8..77b096c 100644 --- a/webserv/handler/ErrorHandler.hpp +++ b/webserv/handler/ErrorHandler.hpp @@ -1,11 +1,15 @@ #pragma once -// #include "webserv/config/AConfig.hpp" -// class ErrorHandler -// { -// public: -// static std::string generateErrorPage(int statusCode, AConfig *config = nullptr); -// static std::string getStatusMessage(int statusCode); -// static bool isValidStatusCode(int statusCode); - -// }; \ No newline at end of file +#include "webserv/config/AConfig.hpp" +class ErrorHandler +{ + public: + static std::string getErrorResponse(int statusCode, AConfig *config = nullptr); + static std::string generateErrorPage(int statusCode, AConfig *config = nullptr); + static std::string generateErrorHeader(int statusCode, const std::string &body); + static std::string generateDefaultErrorPage(int statusCode); + static std::string_view getStatusMessage(int statusCode); + // static bool isValidStatusCode(int statusCode); + static std::string getErrorPageFile(const std::string &path); + +}; \ No newline at end of file diff --git a/webserv/handler/Errorhandler.cpp b/webserv/handler/Errorhandler.cpp index 4b66923..cbb0cae 100644 --- a/webserv/handler/Errorhandler.cpp +++ b/webserv/handler/Errorhandler.cpp @@ -1,19 +1,77 @@ -#include - -#include // for StatusCode #include +#include +#include // for StatusCode #include +#include // for basic_ifstream, basic_filebuf, basic_ostream::operator<<, ifstream, stringstream +#include // for basic_stringstream +#include // for basic_string, to_string, string +#include // for string_view +std::string ErrorHandler::getErrorResponse(int statusCode, AConfig *config) +{ + std::string body = generateErrorPage(statusCode, config); + Log::debug("Generated error page : " + generateErrorHeader(statusCode, body) + body); + return generateErrorHeader(statusCode, body) + body; +} -// std::string ErrorHandler::generateErrorPage(int statusCode, AConfig *config) -// { - -// std::string statusMessage = getStatusMessage(statusCode); -// std::string html = -// "" + std::to_string(statusCode) + " " + statusMessage + -// "

" + std::to_string(statusCode) + " " + statusMessage + -// "


webserv

"; -// return html; -// } +std::string ErrorHandler::generateErrorHeader(int statusCode, const std::string &body) +{ + std::string response = "HTTP/1.1 "; + response += std::to_string(statusCode) + " "; + response += std::string(getStatusMessage(statusCode)) + std::string(Http::Protocol::CRLF); + response += "Content-Type: text/html" + std::string(Http::Protocol::CRLF); + response += "Content-Length: " + std::to_string(body.size()) + std::string(Http::Protocol::DOUBLE_CRLF); // End of headers + return response; +} +std::string ErrorHandler::generateErrorPage(int statusCode, AConfig *config) +{ + + if (config != nullptr) + { + std::string customPage = config->getErrorPage(statusCode); + if (!customPage.empty()) + { + return getErrorPageFile(customPage); + } + } + return generateDefaultErrorPage(statusCode); +} + +std::string ErrorHandler::generateDefaultErrorPage(int statusCode) +{ + Log::info("Generating default error page for status code: " + std::to_string(statusCode)); + + std::string_view statusMessage = getStatusMessage(statusCode); + std::string html = "" + std::to_string(statusCode) + " " + std::string(statusMessage) + + "

" + std::to_string(statusCode) + " " + std::string(statusMessage) + + "


webserv

"; + return generateErrorHeader(statusCode, html) + html; +} + +std::string_view ErrorHandler::getStatusMessage(int statusCode) +{ + for (const auto info : Http::statusCodeInfos) + { + if (info.code == statusCode) + { + return info.reason; + } + } + return "Unknown Status"; +} + +std::string ErrorHandler::getErrorPageFile(const std::string &path) +{ + Log::info("Loading custom error page from: " + path); + std::ifstream file(path.c_str()); + if (!file.is_open()) + { + Log::error("Could not open custom error page: " + path); + return generateErrorPage(Http::StatusCode::INTERNAL_SERVER_ERROR); + } + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); +} \ No newline at end of file diff --git a/webserv/http/HttpConstants.hpp b/webserv/http/HttpConstants.hpp index 415695b..2d5e4aa 100644 --- a/webserv/http/HttpConstants.hpp +++ b/webserv/http/HttpConstants.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace Http { @@ -42,6 +43,27 @@ constexpr uint16_t BAD_GATEWAY = 502; constexpr uint16_t SERVICE_UNAVAILABLE = 503; } // namespace StatusCode +struct StatusCodeInfo +{ + uint16_t code; + std::string_view reason; +}; + +static constexpr std::array statusCodeInfos = {{ + { .code = StatusCode::OK, .reason = "OK" }, + { .code = StatusCode::CREATED, .reason = "Created" }, + { .code = StatusCode::NO_CONTENT, .reason = "No Content" }, + { .code = StatusCode::BAD_REQUEST, .reason = "Bad Request" }, + { .code = StatusCode::UNAUTHORIZED, .reason = "Unauthorized" }, + { .code = StatusCode::FORBIDDEN, .reason = "Forbidden" }, + { .code = StatusCode::NOT_FOUND, .reason = "Not Found" }, + { .code = StatusCode::METHOD_NOT_ALLOWED, .reason = "Method Not Allowed" }, + { .code = StatusCode::INTERNAL_SERVER_ERROR, .reason = "Internal Server Error" }, + { .code = StatusCode::NOT_IMPLEMENTED, .reason = "Not Implemented" }, + { .code = StatusCode::BAD_GATEWAY, .reason = "Bad Gateway" }, + { .code = StatusCode::SERVICE_UNAVAILABLE, .reason = "Service Unavailable" } +}}; + // Header Names namespace Header {