feat: preliminary error handling with custom error pages and response generation
This commit is contained in:
parent
787432ff0c
commit
949c788259
@ -1,4 +1,6 @@
|
|||||||
#include "webserv/config/ConfigManager.hpp"
|
#include "webserv/config/ConfigManager.hpp"
|
||||||
|
#include "webserv/handler/ErrorHandler.hpp"
|
||||||
|
|
||||||
#include <webserv/client/Client.hpp>
|
#include <webserv/client/Client.hpp>
|
||||||
#include <webserv/http/HttpHeaders.hpp> // for HttpHeaders
|
#include <webserv/http/HttpHeaders.hpp> // for HttpHeaders
|
||||||
#include <webserv/log/Log.hpp> // for Log, LOCATION
|
#include <webserv/log/Log.hpp> // for Log, LOCATION
|
||||||
@ -15,8 +17,7 @@
|
|||||||
class ServerConfig;
|
class ServerConfig;
|
||||||
|
|
||||||
Client::Client(std::unique_ptr<Socket> socket, Server &server)
|
Client::Client(std::unique_ptr<Socket> socket, Server &server)
|
||||||
: client_socket_(std::move(socket)), server_(std::ref(server)),
|
: client_socket_(std::move(socket)), server_(std::ref(server)), httpRequest_(std::make_unique<HttpRequest>(this))
|
||||||
httpRequest_(std::make_unique<HttpRequest>(this))
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ void Client::request()
|
|||||||
Log::warning("No matching server config found for Host: " + httpRequest_->getHeaders().get("Host"));
|
Log::warning("No matching server config found for Host: " + httpRequest_->getHeaders().get("Host"));
|
||||||
httpRequest_->setState(HttpRequest::State::ParseError);
|
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());
|
server_.responseReady(client_socket_->getFd());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -89,16 +90,12 @@ void Client::request()
|
|||||||
std::string Client::getResponse() const
|
std::string Client::getResponse() const
|
||||||
{
|
{
|
||||||
Log::trace(LOCATION);
|
Log::trace(LOCATION);
|
||||||
std::string response = "HTTP/1.1 ";
|
|
||||||
if (httpRequest_->getState() == HttpRequest::State::ParseError)
|
if (httpRequest_->getState() == HttpRequest::State::ParseError)
|
||||||
{
|
{
|
||||||
response += "400 Bad Request\r\n";
|
return ErrorHandler::generateErrorPage(Http::StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
else
|
std::string response = "HTTP/1.1 ";
|
||||||
{
|
response += "200 OK\r\n";
|
||||||
response += "200 OK\r\n";
|
|
||||||
|
|
||||||
// further validation can be added here
|
|
||||||
|
|
||||||
auto serverName = server_config_->getDirectiveValue<std::string>("server_name");
|
auto serverName = server_config_->getDirectiveValue<std::string>("server_name");
|
||||||
auto port = server_config_->getDirectiveValue<int>("listen");
|
auto port = server_config_->getDirectiveValue<int>("listen");
|
||||||
@ -106,7 +103,6 @@ std::string Client::getResponse() const
|
|||||||
body += "Server port " + std::to_string(port) + "\r\n";
|
body += "Server port " + std::to_string(port) + "\r\n";
|
||||||
response += "Content-Length: " + std::to_string(body.size()) + "\r\n\r\n";
|
response += "Content-Length: " + std::to_string(body.size()) + "\r\n\r\n";
|
||||||
response += body;
|
response += body;
|
||||||
}
|
|
||||||
|
|
||||||
Log::debug("Sending response:\n" + response);
|
Log::debug("Sending response:\n" + response);
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
@ -14,16 +14,16 @@ void AConfig::addDirective(const std::string &line)
|
|||||||
auto directive = DirectiveFactory::createDirective(line);
|
auto directive = DirectiveFactory::createDirective(line);
|
||||||
if (directive)
|
if (directive)
|
||||||
{
|
{
|
||||||
directives_[directive->getName()] = std::move(directive);
|
directives_.emplace_back(std::move(directive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ADirective *AConfig::getDirective(const std::string &name) const
|
const ADirective *AConfig::getDirective(const std::string &name) const
|
||||||
{
|
{
|
||||||
auto it = directives_.find(name);
|
for (const auto &directive : directives_) {
|
||||||
if (it != directives_.end())
|
if (directive->getName() == name) {
|
||||||
{
|
return directive.get();
|
||||||
return it->second.get();
|
}
|
||||||
}
|
}
|
||||||
if (parent_ != nullptr)
|
if (parent_ != nullptr)
|
||||||
{
|
{
|
||||||
@ -34,9 +34,10 @@ const ADirective *AConfig::getDirective(const std::string &name) const
|
|||||||
|
|
||||||
bool AConfig::hasDirective(const std::string &name) const
|
bool AConfig::hasDirective(const std::string &name) const
|
||||||
{
|
{
|
||||||
if (directives_.contains(name)) // NOLINT
|
for (const auto &directive : directives_) {
|
||||||
{
|
if (directive->getName() == name) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (parent_ != nullptr)
|
if (parent_ != nullptr)
|
||||||
{
|
{
|
||||||
@ -59,7 +60,27 @@ void AConfig::parseDirectives(const std::string &declarations)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Log::info("Global Declaration: " + line);
|
Log::info("Global Declaration: " + line);
|
||||||
auto directive = DirectiveFactory::createDirective(line);
|
addDirective(line);
|
||||||
directives_[directive->getName()] = std::move(directive);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<std::pair<int, std::string>>();
|
||||||
|
if (value.first == statusCode)
|
||||||
|
{
|
||||||
|
return value.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parent_ != nullptr)
|
||||||
|
{
|
||||||
|
return parent_->getErrorPage(statusCode);
|
||||||
|
}
|
||||||
|
return ""; // Return empty string if not found
|
||||||
|
}
|
||||||
@ -21,6 +21,7 @@ class AConfig
|
|||||||
|
|
||||||
void addDirective(const std::string &line);
|
void addDirective(const std::string &line);
|
||||||
[[nodiscard]] const ADirective *getDirective(const std::string &name) const;
|
[[nodiscard]] const ADirective *getDirective(const std::string &name) const;
|
||||||
|
[[nodiscard]] std::string getErrorPage(int statusCode) const;
|
||||||
|
|
||||||
[[nodiscard]] bool hasDirective(const std::string &name) const;
|
[[nodiscard]] bool hasDirective(const std::string &name) const;
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ class AConfig
|
|||||||
protected:
|
protected:
|
||||||
virtual void parseBlock(const std::string &block) = 0;
|
virtual void parseBlock(const std::string &block) = 0;
|
||||||
void parseDirectives(const std::string &declarations);
|
void parseDirectives(const std::string &declarations);
|
||||||
std::map<std::string, std::unique_ptr<ADirective>>
|
std::vector<std::unique_ptr<ADirective>>
|
||||||
directives_; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
|
directives_; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
|
||||||
const AConfig *parent_ = nullptr; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
|
const AConfig *parent_ = nullptr; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
|
||||||
};
|
};
|
||||||
@ -1,11 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// #include "webserv/config/AConfig.hpp"
|
#include "webserv/config/AConfig.hpp"
|
||||||
// class ErrorHandler
|
class ErrorHandler
|
||||||
// {
|
{
|
||||||
// public:
|
public:
|
||||||
// static std::string generateErrorPage(int statusCode, AConfig *config = nullptr);
|
static std::string getErrorResponse(int statusCode, AConfig *config = nullptr);
|
||||||
// static std::string getStatusMessage(int statusCode);
|
static std::string generateErrorPage(int statusCode, AConfig *config = nullptr);
|
||||||
// static bool isValidStatusCode(int statusCode);
|
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);
|
||||||
|
|
||||||
|
};
|
||||||
@ -1,19 +1,77 @@
|
|||||||
#include <webserv/handler/ErrorHandler.hpp>
|
|
||||||
|
|
||||||
#include <webserv/http/HttpConstants.hpp> // for StatusCode
|
|
||||||
#include <webserv/config/AConfig.hpp>
|
#include <webserv/config/AConfig.hpp>
|
||||||
|
#include <webserv/handler/ErrorHandler.hpp>
|
||||||
|
#include <webserv/http/HttpConstants.hpp> // for StatusCode
|
||||||
#include <webserv/log/Log.hpp>
|
#include <webserv/log/Log.hpp>
|
||||||
|
|
||||||
|
#include <fstream> // for basic_ifstream, basic_filebuf, basic_ostream::operator<<, ifstream, stringstream
|
||||||
|
#include <sstream> // for basic_stringstream
|
||||||
|
#include <string> // for basic_string, to_string, string
|
||||||
|
#include <string_view> // 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 ErrorHandler::generateErrorHeader(int statusCode, const std::string &body)
|
||||||
// {
|
{
|
||||||
|
std::string response = "HTTP/1.1 ";
|
||||||
// std::string statusMessage = getStatusMessage(statusCode);
|
response += std::to_string(statusCode) + " ";
|
||||||
// std::string html =
|
response += std::string(getStatusMessage(statusCode)) + std::string(Http::Protocol::CRLF);
|
||||||
// "<html><head><title>" + std::to_string(statusCode) + " " + statusMessage +
|
response += "Content-Type: text/html" + std::string(Http::Protocol::CRLF);
|
||||||
// "</title></head><body><h1>" + std::to_string(statusCode) + " " + statusMessage +
|
response += "Content-Length: " + std::to_string(body.size()) + std::string(Http::Protocol::DOUBLE_CRLF); // End of headers
|
||||||
// "</h1><hr><p>webserv</p></body></html>";
|
return response;
|
||||||
// return html;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
|
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 = "<html><head><title>" + std::to_string(statusCode) + " " + std::string(statusMessage) +
|
||||||
|
"</title></head><body><h1>" + std::to_string(statusCode) + " " + std::string(statusMessage) +
|
||||||
|
"</h1><hr><p>webserv</p></body></html>";
|
||||||
|
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();
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
namespace Http
|
namespace Http
|
||||||
{
|
{
|
||||||
@ -42,6 +43,27 @@ constexpr uint16_t BAD_GATEWAY = 502;
|
|||||||
constexpr uint16_t SERVICE_UNAVAILABLE = 503;
|
constexpr uint16_t SERVICE_UNAVAILABLE = 503;
|
||||||
} // namespace StatusCode
|
} // namespace StatusCode
|
||||||
|
|
||||||
|
struct StatusCodeInfo
|
||||||
|
{
|
||||||
|
uint16_t code;
|
||||||
|
std::string_view reason;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr std::array<StatusCodeInfo, 12> 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
|
// Header Names
|
||||||
namespace Header
|
namespace Header
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user