feat: implement redirect handling and update configuration

This commit is contained in:
whaffman 2025-10-28 22:02:11 +01:00
parent 2fce81c2e1
commit 9e773e43fd
9 changed files with 145 additions and 46 deletions

View File

@ -54,6 +54,10 @@ server {
index index.html; index index.html;
allowed_methods GET; allowed_methods GET;
} }
location /redirect {
redirect 301 http://localhost:8081/;
allowed_methods GET POST;
}
# cgi_enabled yes; # cgi_enabled yes;
# cgi_ext .php /usr/bin/php-cgi; # cgi_ext .php /usr/bin/php-cgi;

View File

@ -1,12 +1,10 @@
#include <webserv/client/Client.hpp> #include <webserv/client/Client.hpp>
#include <webserv/http/RequestValidator.hpp>
#include <webserv/handler/CgiHandler.hpp> // for CgiHandler #include <webserv/handler/CgiHandler.hpp> // for CgiHandler
#include <webserv/handler/ErrorHandler.hpp> // for ErrorHandler #include <webserv/handler/ErrorHandler.hpp> // for ErrorHandler
#include <webserv/http/HttpHeaders.hpp> // for HttpHeaders #include <webserv/http/HttpHeaders.hpp> // for HttpHeaders
#include <webserv/http/HttpRequest.hpp> // for HttpRequest #include <webserv/http/HttpRequest.hpp> // for HttpRequest
#include <webserv/http/HttpResponse.hpp> // for HttpResponse #include <webserv/http/HttpResponse.hpp> // for HttpResponse
#include <webserv/http/RequestValidator.hpp>
#include <webserv/log/Log.hpp> // for Log, LOCATION #include <webserv/log/Log.hpp> // for Log, LOCATION
#include <webserv/router/Router.hpp> // for Router #include <webserv/router/Router.hpp> // for Router
#include <webserv/server/Server.hpp> // for Server #include <webserv/server/Server.hpp> // for Server
@ -84,7 +82,10 @@ void Client::request()
if (httpRequest_->getState() == HttpRequest::State::Complete if (httpRequest_->getState() == HttpRequest::State::Complete
|| httpRequest_->getState() == HttpRequest::State::ParseError) || httpRequest_->getState() == HttpRequest::State::ParseError)
{ {
Log::info("Received complete request", Log::info("Received request: " + httpRequest_->getHttpVersion() + " " + httpRequest_->getMethod() + " "
+ httpRequest_->getTarget() + " ");
Log::debug("Request details",
{ {
{"request_method", httpRequest_->getMethod()}, {"request_method", httpRequest_->getMethod()},
{"request_target", httpRequest_->getTarget()}, {"request_target", httpRequest_->getTarget()},
@ -96,10 +97,10 @@ void Client::request()
try try
{ {
// Thoughts: if a handler isn't returned, this could because of the error handler already setting up the // Thoughts: if a handler isn't returned, this could because of the error handler already setting
// response so, maybe we don't need to throw a 500 when no handler. Because that would override the actual // up the response so, maybe we don't need to throw a 500 when no handler. Because that would
// error response. How about the router, or a handler, throws an exception if something goes wrong, and we // override the actual error response. How about the router, or a handler, throws an exception if
// catch it here to make a 500 response? // something goes wrong, and we catch it here to make a 500 response?
handler_ = router_->handleRequest(); handler_ = router_->handleRequest();
if (handler_ != nullptr) if (handler_ != nullptr)
{ {
@ -150,7 +151,7 @@ void Client::poll() const
// CGI handler polling logic if needed // CGI handler polling logic if needed
cgiHandler->wait(); cgiHandler->wait();
} }
if (httpResponse_->isComplete()) if (httpResponse_->isComplete() && clientSocket_->getEvent() != ASocket::IoState::WRITE)
{ {
Log::info("Response is ready to be sent to client, fd: " + std::to_string(clientSocket_->getFd())); Log::info("Response is ready to be sent to client, fd: " + std::to_string(clientSocket_->getFd()));
clientSocket_->setCallback([this]() { respond(); }); clientSocket_->setCallback([this]() { respond(); });

View File

@ -35,7 +35,7 @@ class DirectiveFactory
{.name = "cgi_timeout", .type = "IntDirective", .context = "gsl"}, {.name = "cgi_timeout", .type = "IntDirective", .context = "gsl"},
{.name = "upload_enabled", .type = "BoolDirective", .context = "gsl"}, {.name = "upload_enabled", .type = "BoolDirective", .context = "gsl"},
{.name = "upload_store", .type = "StringDirective", .context = "gsl"}, {.name = "upload_store", .type = "StringDirective", .context = "gsl"},
{.name = "redirect", .type = "VectorDirective", .context = "l"}, {.name = "redirect", .type = "IntStringDirective", .context = "l"},
{.name = "timeout", .type = "IntDirective", .context = "gsl"}, {.name = "timeout", .type = "IntDirective", .context = "gsl"},
}}; }};

View File

@ -0,0 +1,40 @@
#include <webserv/handler/RedirectHandler.hpp> // for RedirectHandler
#include <webserv/http/HttpRequest.hpp> // for HttpRequest
#include <webserv/http/HttpResponse.hpp> // for HttpResponse
#include <webserv/log/Log.hpp> // for Log, LOCATION
#include <webserv/handler/URI.hpp> // for URI
#include <webserv/handler/ErrorHandler.hpp> // for ErrorHandler
#include <webserv/http/HttpConstants.hpp> // for HttpConstants
RedirectHandler::RedirectHandler(const HttpRequest &request, HttpResponse &response)
: AHandler(request, response)
{
}
void RedirectHandler::handle()
{
if (request_.getUri().isRedirect())
{
Log::info("Redirecting request to: " + request_.getUri().getRedirect().second + " with reason: "
+ Http::getStatusCodeReason(request_.getUri().getRedirect().first));
std::pair<int, std::string> redirect = request_.getUri().getRedirect();
response_.setStatus(redirect.first);
response_.addHeader(std::string(Http::Header::REDIRECT_LOCATION), redirect.second);
response_.addHeader(std::string(Http::Header::CONTENT_TYPE), std::string(Http::MimeType::TEXT_HTML));
response_.addHeader(std::string(Http::Header::CACHE_CONTROL), "no-cache");
std::string body = "<html><head><title>" + std::to_string(redirect.first) + " " + Http::getStatusCodeReason(redirect.first) + "</title></head>"
"<body><a href=\"" +
redirect.second + "\">Found</a></body></html>";
response_.setBody(body);
}
}
void RedirectHandler::handleTimeout()
{
Log::warning("Redirect handler timeout occurred for path: " + request_.getUri().getFullPath());
ErrorHandler::createErrorResponse(504, response_, request_.getUri().getConfig());
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <webserv/handler/AHandler.hpp> // for AHandler
class RedirectHandler : public AHandler
{
public:
RedirectHandler(const HttpRequest &request, HttpResponse &response);
void handle() override;
protected:
void handleTimeout() override;
private:
};

View File

@ -1,8 +1,7 @@
#include <webserv/handler/URI.hpp>
#include <webserv/config/AConfig.hpp> // for AConfig #include <webserv/config/AConfig.hpp> // for AConfig
#include <webserv/config/LocationConfig.hpp> // for LocationConfig #include <webserv/config/LocationConfig.hpp> // for LocationConfig
#include <webserv/config/ServerConfig.hpp> // for ServerConfig #include <webserv/config/ServerConfig.hpp> // for ServerConfig
#include <webserv/handler/URI.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
#include <webserv/utils/FileUtils.hpp> // for joinPath, isDirectory, isFile, getExtension, isValidPath #include <webserv/utils/FileUtils.hpp> // for joinPath, isDirectory, isFile, getExtension, isValidPath
@ -183,6 +182,22 @@ bool URI::isCgi() const noexcept
return !getCgiPath().empty(); return !getCgiPath().empty();
} }
bool URI::isRedirect() const noexcept
{
auto redirectOpt = config_->get<std::pair<int, std::string>>("redirect");
return redirectOpt.has_value();
}
std::pair<int, std::string> URI::getRedirect() const
{
auto redirectOpt = config_->get<std::pair<int, std::string>>("redirect");
if (redirectOpt.has_value())
{
return redirectOpt.value();
}
return {0, ""};
}
std::string URI::getCgiPath() const std::string URI::getCgiPath() const
{ {
// Log::debug("BaseName: " + baseName_ + ", FullPath: " + fullPath_ + ", Dir: " + dir_ + ", PathInfo: " + pathInfo_ // Log::debug("BaseName: " + baseName_ + ", FullPath: " + fullPath_ + ", Dir: " + dir_ + ", PathInfo: " + pathInfo_

View File

@ -21,9 +21,12 @@ class URI
[[nodiscard]] bool isDirectory() const noexcept; [[nodiscard]] bool isDirectory() const noexcept;
[[nodiscard]] bool isValid() const noexcept; [[nodiscard]] bool isValid() const noexcept;
[[nodiscard]] bool isCgi() const noexcept; [[nodiscard]] bool isCgi() const noexcept;
[[nodiscard]] bool isRedirect() const noexcept;
[[nodiscard]] std::string getExtension() const noexcept; [[nodiscard]] std::string getExtension() const noexcept;
[[nodiscard]] std::string getCgiPath() const; [[nodiscard]] std::string getCgiPath() const;
[[nodiscard]] std::pair<int, std::string> getRedirect() const;
[[nodiscard]] const AConfig *getConfig() const noexcept; [[nodiscard]] const AConfig *getConfig() const noexcept;
[[nodiscard]] const std::string &getBaseName() const noexcept; [[nodiscard]] const std::string &getBaseName() const noexcept;
[[nodiscard]] const std::string &getFullPath() const noexcept; [[nodiscard]] const std::string &getFullPath() const noexcept;

View File

@ -35,6 +35,13 @@ namespace StatusCode
constexpr uint16_t OK = 200; constexpr uint16_t OK = 200;
constexpr uint16_t CREATED = 201; constexpr uint16_t CREATED = 201;
constexpr uint16_t NO_CONTENT = 204; constexpr uint16_t NO_CONTENT = 204;
constexpr uint16_t MOVED_PERMANENTLY = 301;
constexpr uint16_t FOUND = 302;
constexpr uint16_t SEE_OTHER = 303;
constexpr uint16_t TEMPORARY_REDIRECT = 307;
constexpr uint16_t PERMANENT_REDIRECT = 308;
constexpr uint16_t BAD_REQUEST = 400; constexpr uint16_t BAD_REQUEST = 400;
constexpr uint16_t UNAUTHORIZED = 401; constexpr uint16_t UNAUTHORIZED = 401;
constexpr uint16_t FORBIDDEN = 403; constexpr uint16_t FORBIDDEN = 403;
@ -53,10 +60,15 @@ struct StatusCodeInfo
std::string_view reason; std::string_view reason;
}; };
static constexpr std::array<StatusCodeInfo, 13> statusCodeInfos static constexpr std::array<StatusCodeInfo, 18> statusCodeInfos
= {{{.code = StatusCode::OK, .reason = "OK"}, = {{{.code = StatusCode::OK, .reason = "OK"},
{.code = StatusCode::CREATED, .reason = "Created"}, {.code = StatusCode::CREATED, .reason = "Created"},
{.code = StatusCode::NO_CONTENT, .reason = "No Content"}, {.code = StatusCode::NO_CONTENT, .reason = "No Content"},
{.code = StatusCode::MOVED_PERMANENTLY, .reason = "Moved Permanently"},
{.code = StatusCode::FOUND, .reason = "Found"},
{.code = StatusCode::SEE_OTHER, .reason = "See Other"},
{.code = StatusCode::TEMPORARY_REDIRECT, .reason = "Temporary Redirect"},
{.code = StatusCode::PERMANENT_REDIRECT, .reason = "Permanent Redirect"},
{.code = StatusCode::BAD_REQUEST, .reason = "Bad Request"}, {.code = StatusCode::BAD_REQUEST, .reason = "Bad Request"},
{.code = StatusCode::UNAUTHORIZED, .reason = "Unauthorized"}, {.code = StatusCode::UNAUTHORIZED, .reason = "Unauthorized"},
{.code = StatusCode::FORBIDDEN, .reason = "Forbidden"}, {.code = StatusCode::FORBIDDEN, .reason = "Forbidden"},
@ -75,6 +87,7 @@ namespace Header
{ {
constexpr std::string_view CONTENT_TYPE = "Content-Type"; constexpr std::string_view CONTENT_TYPE = "Content-Type";
constexpr std::string_view CONTENT_LENGTH = "Content-Length"; constexpr std::string_view CONTENT_LENGTH = "Content-Length";
constexpr std::string_view REDIRECT_LOCATION = "Location";
constexpr std::string_view HOST = "Host"; constexpr std::string_view HOST = "Host";
constexpr std::string_view USER_AGENT = "User-Agent"; constexpr std::string_view USER_AGENT = "User-Agent";
constexpr std::string_view ACCEPT = "Accept"; constexpr std::string_view ACCEPT = "Accept";

View File

@ -13,6 +13,7 @@
#include <webserv/handler/URI.hpp> // for URI #include <webserv/handler/URI.hpp> // for URI
#include <webserv/http/HttpRequest.hpp> // for HttpRequest #include <webserv/http/HttpRequest.hpp> // for HttpRequest
#include <webserv/log/Log.hpp> // for Log, LOCATION #include <webserv/log/Log.hpp> // for Log, LOCATION
#include <webserv/handler/RedirectHandler.hpp> // for RedirectHandler
#include <exception> // for exception #include <exception> // for exception
#include <format> // for vector #include <format> // for vector
@ -61,6 +62,10 @@ std::unique_ptr<AHandler> Router::handleRequest()
Log::warning("Request validation failed: " + error->message); Log::warning("Request validation failed: " + error->message);
throw RequestValidator::ValidationException{error->statusCode}; throw RequestValidator::ValidationException{error->statusCode};
} }
if (request.getUri().isRedirect())
{
return std::make_unique<RedirectHandler>(request, response);
}
if (request.getUri().isCgi()) if (request.getUri().isCgi())
{ {
try try