diff --git a/config/default.conf b/config/default.conf index a4dbcec..b189ab0 100644 --- a/config/default.conf +++ b/config/default.conf @@ -54,6 +54,10 @@ server { index index.html; allowed_methods GET; } + location /redirect { + redirect 301 http://localhost:8081/; + allowed_methods GET POST; + } # cgi_enabled yes; # cgi_ext .php /usr/bin/php-cgi; diff --git a/webserv/client/Client.cpp b/webserv/client/Client.cpp index 3713809..2742c95 100644 --- a/webserv/client/Client.cpp +++ b/webserv/client/Client.cpp @@ -1,17 +1,15 @@ #include - -#include - #include // for CgiHandler #include // for ErrorHandler #include // for HttpHeaders #include // for HttpRequest #include // for HttpResponse -#include // for Log, LOCATION -#include // for Router -#include // for Server -#include // for ASocket -#include // for ClientSocket +#include +#include // for Log, LOCATION +#include // for Router +#include // for Server +#include // for ASocket +#include // for ClientSocket #include // for uint8_t #include // for function, ref, reference_wrapper @@ -84,40 +82,43 @@ void Client::request() if (httpRequest_->getState() == HttpRequest::State::Complete || httpRequest_->getState() == HttpRequest::State::ParseError) { - Log::info("Received complete request", - { - {"request_method", httpRequest_->getMethod()}, - {"request_target", httpRequest_->getTarget()}, - {"http_version", httpRequest_->getHttpVersion()}, - {"headers", httpRequest_->getHeaders().toString()}, - {"body", httpRequest_->getBody()}, - {"state", std::to_string(static_cast(httpRequest_->getState()))}, - }); + Log::info("Received request: " + httpRequest_->getHttpVersion() + " " + httpRequest_->getMethod() + " " + + httpRequest_->getTarget() + " "); + + Log::debug("Request details", + { + {"request_method", httpRequest_->getMethod()}, + {"request_target", httpRequest_->getTarget()}, + {"http_version", httpRequest_->getHttpVersion()}, + {"headers", httpRequest_->getHeaders().toString()}, + {"body", httpRequest_->getBody()}, + {"state", std::to_string(static_cast(httpRequest_->getState()))}, + }); - try - { - // Thoughts: if a handler isn't returned, this could because of the error handler already setting up the - // response so, maybe we don't need to throw a 500 when no handler. Because that would override the actual - // error response. How about the router, or a handler, throws an exception if something goes wrong, and we - // catch it here to make a 500 response? - handler_ = router_->handleRequest(); - if (handler_ != nullptr) - { - handler_->handle(); - } - } - catch (const RequestValidator::ValidationException &e) - { - Log::error("Exception during request handling: " + std::string(e.what())); - ErrorHandler::createErrorResponse(e.code(), *httpResponse_); - return; - } - catch (const std::exception &e) - { - Log::error("Exception during request handling: " + std::string(e.what())); - ErrorHandler::createErrorResponse(500, *httpResponse_); - return; - } + try + { + // Thoughts: if a handler isn't returned, this could because of the error handler already setting + // up the response so, maybe we don't need to throw a 500 when no handler. Because that would + // override the actual error response. How about the router, or a handler, throws an exception if + // something goes wrong, and we catch it here to make a 500 response? + handler_ = router_->handleRequest(); + if (handler_ != nullptr) + { + handler_->handle(); + } + } + catch (const RequestValidator::ValidationException &e) + { + Log::error("Exception during request handling: " + std::string(e.what())); + ErrorHandler::createErrorResponse(e.code(), *httpResponse_); + return; + } + catch (const std::exception &e) + { + Log::error("Exception during request handling: " + std::string(e.what())); + ErrorHandler::createErrorResponse(500, *httpResponse_); + return; + } } else { @@ -150,7 +151,7 @@ void Client::poll() const // CGI handler polling logic if needed 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())); clientSocket_->setCallback([this]() { respond(); }); diff --git a/webserv/config/directive/DirectiveFactory.hpp b/webserv/config/directive/DirectiveFactory.hpp index ecbaeb9..966b695 100644 --- a/webserv/config/directive/DirectiveFactory.hpp +++ b/webserv/config/directive/DirectiveFactory.hpp @@ -35,7 +35,7 @@ class DirectiveFactory {.name = "cgi_timeout", .type = "IntDirective", .context = "gsl"}, {.name = "upload_enabled", .type = "BoolDirective", .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"}, }}; diff --git a/webserv/handler/RedirectHandler.cpp b/webserv/handler/RedirectHandler.cpp new file mode 100644 index 0000000..8dc8823 --- /dev/null +++ b/webserv/handler/RedirectHandler.cpp @@ -0,0 +1,40 @@ +#include // for RedirectHandler +#include // for HttpRequest +#include // for HttpResponse +#include // for Log, LOCATION +#include // for URI +#include // for ErrorHandler +#include // 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 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 = "" + std::to_string(redirect.first) + " " + Http::getStatusCodeReason(redirect.first) + "" + "Found"; + response_.setBody(body); + + } +} + +void RedirectHandler::handleTimeout() +{ + + Log::warning("Redirect handler timeout occurred for path: " + request_.getUri().getFullPath()); + ErrorHandler::createErrorResponse(504, response_, request_.getUri().getConfig()); +} + diff --git a/webserv/handler/RedirectHandler.hpp b/webserv/handler/RedirectHandler.hpp new file mode 100644 index 0000000..6a4e7c8 --- /dev/null +++ b/webserv/handler/RedirectHandler.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include // for AHandler + +class RedirectHandler : public AHandler +{ + public: + RedirectHandler(const HttpRequest &request, HttpResponse &response); + + + void handle() override; + + protected: + void handleTimeout() override; + + private: + +}; diff --git a/webserv/handler/URI.cpp b/webserv/handler/URI.cpp index f980fa6..85dfa82 100644 --- a/webserv/handler/URI.cpp +++ b/webserv/handler/URI.cpp @@ -1,8 +1,7 @@ -#include - #include // for AConfig #include // for LocationConfig #include // for ServerConfig +#include #include // for HttpHeaders #include // for Log, LOCATION #include // for joinPath, isDirectory, isFile, getExtension, isValidPath @@ -183,6 +182,22 @@ bool URI::isCgi() const noexcept return !getCgiPath().empty(); } +bool URI::isRedirect() const noexcept +{ + auto redirectOpt = config_->get>("redirect"); + return redirectOpt.has_value(); +} + +std::pair URI::getRedirect() const +{ + auto redirectOpt = config_->get>("redirect"); + if (redirectOpt.has_value()) + { + return redirectOpt.value(); + } + return {0, ""}; +} + std::string URI::getCgiPath() const { // Log::debug("BaseName: " + baseName_ + ", FullPath: " + fullPath_ + ", Dir: " + dir_ + ", PathInfo: " + pathInfo_ diff --git a/webserv/handler/URI.hpp b/webserv/handler/URI.hpp index 5e5c168..dcf02aa 100644 --- a/webserv/handler/URI.hpp +++ b/webserv/handler/URI.hpp @@ -21,9 +21,12 @@ class URI [[nodiscard]] bool isDirectory() const noexcept; [[nodiscard]] bool isValid() const noexcept; [[nodiscard]] bool isCgi() const noexcept; + [[nodiscard]] bool isRedirect() const noexcept; [[nodiscard]] std::string getExtension() const noexcept; [[nodiscard]] std::string getCgiPath() const; + [[nodiscard]] std::pair getRedirect() const; + [[nodiscard]] const AConfig *getConfig() const noexcept; [[nodiscard]] const std::string &getBaseName() const noexcept; [[nodiscard]] const std::string &getFullPath() const noexcept; diff --git a/webserv/http/HttpConstants.hpp b/webserv/http/HttpConstants.hpp index 325cfb7..009ff39 100644 --- a/webserv/http/HttpConstants.hpp +++ b/webserv/http/HttpConstants.hpp @@ -35,6 +35,13 @@ namespace StatusCode constexpr uint16_t OK = 200; constexpr uint16_t CREATED = 201; 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 UNAUTHORIZED = 401; constexpr uint16_t FORBIDDEN = 403; @@ -53,10 +60,15 @@ struct StatusCodeInfo std::string_view reason; }; -static constexpr std::array statusCodeInfos +static constexpr std::array statusCodeInfos = {{{.code = StatusCode::OK, .reason = "OK"}, {.code = StatusCode::CREATED, .reason = "Created"}, {.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::UNAUTHORIZED, .reason = "Unauthorized"}, {.code = StatusCode::FORBIDDEN, .reason = "Forbidden"}, @@ -75,6 +87,7 @@ namespace Header { constexpr std::string_view CONTENT_TYPE = "Content-Type"; 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 USER_AGENT = "User-Agent"; constexpr std::string_view ACCEPT = "Accept"; diff --git a/webserv/router/Router.cpp b/webserv/router/Router.cpp index 16750df..44df7cb 100644 --- a/webserv/router/Router.cpp +++ b/webserv/router/Router.cpp @@ -13,6 +13,7 @@ #include // for URI #include // for HttpRequest #include // for Log, LOCATION +#include // for RedirectHandler #include // for exception #include // for vector @@ -61,6 +62,10 @@ std::unique_ptr Router::handleRequest() Log::warning("Request validation failed: " + error->message); throw RequestValidator::ValidationException{error->statusCode}; } + if (request.getUri().isRedirect()) + { + return std::make_unique(request, response); + } if (request.getUri().isCgi()) { try