From e7e913a32d92094bcd97d694ec6f7836f72ffb15 Mon Sep 17 00:00:00 2001 From: Quinten Date: Tue, 21 Oct 2025 22:17:17 +0200 Subject: [PATCH] timeout, but please refactor yes thankyou --- htdocs/site-3/index.php | 2 ++ webserv/client/Client.cpp | 4 +-- webserv/handler/AHandler.cpp | 26 ++++++++++++++++++- webserv/handler/AHandler.hpp | 27 +++++++++++-------- webserv/handler/CgiHandler.cpp | 20 ++++++++++++++ webserv/handler/CgiHandler.hpp | 3 +++ webserv/handler/CgiProcess.cpp | 8 +++--- webserv/handler/FileHandler.cpp | 6 +++++ webserv/handler/FileHandler.hpp | 3 +++ webserv/log/Log.hpp | 2 +- webserv/server/Server.cpp | 16 +++++++++--- webserv/socket/ASocket.cpp | 6 ++--- webserv/socket/ASocket.hpp | 13 +++++----- webserv/socket/CgiSocket.cpp | 2 +- webserv/socket/CgiSocket.hpp | 2 +- webserv/socket/ServerSocket.cpp | 2 +- webserv/socket/TimerSocket.cpp | 46 +++++++++++++++++++++++++++++++++ webserv/socket/TimerSocket.hpp | 18 +++++++++++++ webserv/utils/utils.cpp | 8 +++--- webserv/utils/utils.hpp | 2 +- 20 files changed, 177 insertions(+), 39 deletions(-) create mode 100644 webserv/socket/TimerSocket.cpp create mode 100644 webserv/socket/TimerSocket.hpp diff --git a/htdocs/site-3/index.php b/htdocs/site-3/index.php index 3448fdb..1303a02 100644 --- a/htdocs/site-3/index.php +++ b/htdocs/site-3/index.php @@ -13,6 +13,8 @@ if (!isset($_SESSION['request_count'])) { // Increment on each request $_SESSION['request_count']++; +sleep(8); // Simulate some processing delay + ?> diff --git a/webserv/client/Client.cpp b/webserv/client/Client.cpp index 76fc88b..c16cbfa 100644 --- a/webserv/client/Client.cpp +++ b/webserv/client/Client.cpp @@ -93,7 +93,7 @@ void Client::request() } else { - ErrorHandler::createErrorResponse(500, *httpResponse_); + ErrorHandler::createErrorResponse(500, *httpResponse_); } } else @@ -149,7 +149,7 @@ void Client::poll() const Log::info("Response is ready to be sent to client, fd: " + std::to_string(clientSocket_->getFd())); clientSocket_->setCallback([this]() { respond(); }); // server_.writable(clientSocket_->getFd()); - clientSocket_->setIOState(ASocket::IOState::WRITE); + clientSocket_->setIOState(ASocket::IoState::WRITE); } } diff --git a/webserv/handler/AHandler.cpp b/webserv/handler/AHandler.cpp index bbb2f7d..e119daf 100644 --- a/webserv/handler/AHandler.cpp +++ b/webserv/handler/AHandler.cpp @@ -1,7 +1,31 @@ +#include "webserv/log/Log.hpp" #include - #include #include +#include + +#include +#include +#include AHandler::AHandler(const HttpRequest &request, HttpResponse &response) : request_(request), response_(response) {} +void AHandler::startTimer() +{ + timerSocket_ = std::make_unique(std::chrono::milliseconds(5000)); + + timerSocket_->setCallback([this]() { handleTimeout(); }); + timerSocket_->activate(); + + request_.getClient().addSocket(timerSocket_.get()); + Log::debug("Timer started for handler: " + std::to_string(timerSocket_->getFd())); +} + +void AHandler::cancelTimer() +{ + if (timerSocket_) + { + request_.getClient().removeSocket(timerSocket_.get()); + timerSocket_ = nullptr; + } +} \ No newline at end of file diff --git a/webserv/handler/AHandler.hpp b/webserv/handler/AHandler.hpp index 1a5b89c..9cff463 100644 --- a/webserv/handler/AHandler.hpp +++ b/webserv/handler/AHandler.hpp @@ -1,26 +1,31 @@ #pragma once +#include "webserv/socket/TimerSocket.hpp" + +#include class HttpRequest; class HttpResponse; class AHandler { public: - AHandler(const HttpRequest &request, HttpResponse &response); - virtual ~AHandler() = default; + AHandler(const HttpRequest &request, HttpResponse &response); + virtual ~AHandler() = default; - AHandler(const AHandler &other) = delete; - AHandler &operator=(const AHandler &other) = delete; - AHandler(AHandler &&other) noexcept = delete; - AHandler &operator=(AHandler &&other) noexcept = delete; + AHandler(const AHandler &other) = delete; + AHandler &operator=(const AHandler &other) = delete; + AHandler(AHandler &&other) noexcept = delete; + AHandler &operator=(AHandler &&other) noexcept = delete; virtual void handle() = 0; - - protected: - const HttpRequest &request_; - HttpResponse &response_; - + void startTimer(); + void cancelTimer(); + protected: + virtual void handleTimeout() = 0; + const HttpRequest &request_; + HttpResponse &response_; + std::unique_ptr timerSocket_; }; \ No newline at end of file diff --git a/webserv/handler/CgiHandler.cpp b/webserv/handler/CgiHandler.cpp index 4ac98f9..f3ac0ad 100644 --- a/webserv/handler/CgiHandler.cpp +++ b/webserv/handler/CgiHandler.cpp @@ -1,3 +1,4 @@ +#include "webserv/handler/ErrorHandler.hpp" #include // for Client #include #include // for CgiProcess @@ -20,6 +21,8 @@ void CgiHandler::handle() // Initialize CGI process cgiProcess_ = std::make_unique(request_, *this); + startTimer(); + Log::info("CGI process started and sockets registered"); } @@ -71,6 +74,7 @@ void CgiHandler::read() { Log::info("CGI process closed stdout, fd: " + std::to_string(cgiStdOut_->getFd())); request_.getClient().removeSocket(cgiStdOut_.get()); + request_.getClient().removeSocket(timerSocket_.get()); cgiStdOut_ = nullptr; parseCgiOutput(); return; @@ -103,6 +107,7 @@ void CgiHandler::error() { Log::info("CGI process closed stderr, fd: " + std::to_string(cgiStdErr_->getFd())); request_.getClient().removeSocket(cgiStdErr_.get()); + request_.getClient().removeSocket(timerSocket_.get()); // todo maybe this dangerous cgiStdErr_ = nullptr; return; } @@ -213,6 +218,21 @@ void CgiHandler::parseCgiHeaders(std::string &headers) } } +void CgiHandler::handleTimeout() +{ + Log::warning("CGI handler timeout occurred for PID: " + std::to_string(pid_)); + + // Terminate the CGI process if it's still running + if (cgiProcess_) + { + cgiProcess_->kill(); + Log::info("Terminated CGI process with PID: " + std::to_string(pid_)); + } + + ErrorHandler::createErrorResponse(504, response_); + // cancelTimer(); +} + void CgiHandler::parseCgiBody() { Log::trace(LOCATION); diff --git a/webserv/handler/CgiHandler.hpp b/webserv/handler/CgiHandler.hpp index 5d414e4..2078628 100644 --- a/webserv/handler/CgiHandler.hpp +++ b/webserv/handler/CgiHandler.hpp @@ -24,6 +24,9 @@ class CgiHandler : public AHandler void setCgiSockets(std::unique_ptr cgiStdIn, std::unique_ptr cgiStdOut, std::unique_ptr cgiStdErr); void setPid(int pid); + protected: + void handleTimeout() override; + private: constexpr static size_t bufferSize_ = 8192; // TODO: remove duplicate definition and move to configmanager std::vector buffer_; diff --git a/webserv/handler/CgiProcess.cpp b/webserv/handler/CgiProcess.cpp index 39bf5ec..954efa9 100644 --- a/webserv/handler/CgiProcess.cpp +++ b/webserv/handler/CgiProcess.cpp @@ -82,9 +82,9 @@ void CgiProcess::spawn() else { // Parent process - auto cgiStdIn = std::make_unique(pipeStdin[1], ASocket::IOState::WRITE); - auto cgiStdOut = std::make_unique(pipeStdout[0], ASocket::IOState::READ); - auto cgiStdErr = std::make_unique(pipeStderr[0], ASocket::IOState::READ); + auto cgiStdIn = std::make_unique(pipeStdin[1], ASocket::IoState::WRITE); + auto cgiStdOut = std::make_unique(pipeStdout[0], ASocket::IoState::READ); + auto cgiStdErr = std::make_unique(pipeStderr[0], ASocket::IoState::READ); close(pipeStdin[0]); close(pipeStdout[1]); @@ -95,7 +95,7 @@ void CgiProcess::spawn() // request_.getClient().setCgiSockets(std::move(cgiStdIn), std::move(cgiStdOut)); // move the sockets to the // client handler_.setCgiSockets(std::move(cgiStdIn), std::move(cgiStdOut), std::move(cgiStdErr)); - + handler_.setPid(_pid); } } diff --git a/webserv/handler/FileHandler.cpp b/webserv/handler/FileHandler.cpp index 27fb190..16cd337 100644 --- a/webserv/handler/FileHandler.cpp +++ b/webserv/handler/FileHandler.cpp @@ -20,6 +20,12 @@ FileHandler::FileHandler(const HttpRequest &request, HttpResponse &response) Log::trace(LOCATION); } +void FileHandler::handleTimeout() +{ + Log::warning("File handler timeout occurred for path: " + uri_.getFullPath()); + ErrorHandler::createErrorResponse(504, response_, config_); +} + void FileHandler::handleFile(const std::string &filepath) const { Log::trace(LOCATION); diff --git a/webserv/handler/FileHandler.hpp b/webserv/handler/FileHandler.hpp index 8903258..0f604ba 100644 --- a/webserv/handler/FileHandler.hpp +++ b/webserv/handler/FileHandler.hpp @@ -29,6 +29,9 @@ class FileHandler : public AHandler void handle() override; + protected: + void handleTimeout() override; + private: const URI &uri_; const AConfig *config_; diff --git a/webserv/log/Log.hpp b/webserv/log/Log.hpp index bc8f70e..a055ea1 100644 --- a/webserv/log/Log.hpp +++ b/webserv/log/Log.hpp @@ -49,7 +49,7 @@ class Log void log(Level level, const std::string &message, const std::map &context); - static constexpr Log::Level COMPILE_TIME_LOG_LEVEL = Log::Level::Trace; + static constexpr Log::Level COMPILE_TIME_LOG_LEVEL = Log::Level::Debug; static void setFileChannel(const std::string &filename, std::ios_base::openmode mode = std::ios_base::app); static void setStdoutChannel(); diff --git a/webserv/server/Server.cpp b/webserv/server/Server.cpp index 10b66f7..34de21f 100644 --- a/webserv/server/Server.cpp +++ b/webserv/server/Server.cpp @@ -89,7 +89,12 @@ void Server::remove(ASocket &socket) int fd = socket.getFd(); if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr) == -1) { - Log::error("epoll_ctl DEL failed for fd: " + std::to_string(fd)); + if (errno == EBADF || errno == ENOENT) + { + Log::debug("Socket fd " + std::to_string(fd) + " was already closed or removed from epoll"); + return; + } + Log::error("epoll_ctl DEL failed for fd: " + std::to_string(fd) + " with error: " + std::strerror(errno)); throw std::runtime_error("epoll_ctl DEL failed"); } socketToClient_.erase(fd); @@ -133,7 +138,7 @@ void Server::handleConnection(struct epoll_event *event) Log::trace(LOCATION); ServerSocket &listener = getListener(event->data.fd); std::unique_ptr clientSocket = listener.accept(); - + clientSocket->setIOState(ASocket::IoState::READ); auto client = std::make_unique(std::move(clientSocket), *this); add(client->getSocket(), client.get()); clients_.emplace_back(std::move(client)); @@ -198,7 +203,12 @@ void Server::update(const ASocket &socket) const evt.data.fd = socketFd; if (epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, socketFd, &evt) == -1) { - Log::error("epoll_ctl MOD failed for fd: " + std::to_string(socketFd)); + if (errno == EBADF || errno == ENOENT) + { + Log::debug("Socket fd " + std::to_string(socketFd) + " was already closed or removed from epoll"); + return; + } + Log::error("epoll_ctl MOD failed for fd: " + std::to_string(socketFd) + " with error: " + std::strerror(errno)); throw std::runtime_error("epoll_ctl MOD failed"); } } diff --git a/webserv/socket/ASocket.cpp b/webserv/socket/ASocket.cpp index 2e6fb71..0ffb27b 100644 --- a/webserv/socket/ASocket.cpp +++ b/webserv/socket/ASocket.cpp @@ -10,7 +10,7 @@ #include // for recv, send #include // for close -ASocket::ASocket(int fd, IOState event) : fd_(fd), ioState_(event) +ASocket::ASocket(int fd, IoState event) : fd_(fd), ioState_(event) { Log::trace(LOCATION); if (fd_ == -1) @@ -65,7 +65,7 @@ int ASocket::getFd() const noexcept return fd_; } -ASocket::IOState ASocket::getEvent() const noexcept +ASocket::IoState ASocket::getEvent() const noexcept { return ioState_; } @@ -75,7 +75,7 @@ bool ASocket::isDirty() const noexcept return dirty_; } -void ASocket::setIOState(IOState event) +void ASocket::setIOState(IoState event) { if (event == ioState_) { diff --git a/webserv/socket/ASocket.hpp b/webserv/socket/ASocket.hpp index 5a9ab06..b0d3bb4 100644 --- a/webserv/socket/ASocket.hpp +++ b/webserv/socket/ASocket.hpp @@ -13,10 +13,11 @@ class ASocket { CLIENT_SOCKET, SERVER_SOCKET, - CGI_SOCKET + CGI_SOCKET, + TIMER_SOCKET }; - enum class IOState : uint32_t + enum class IoState : uint32_t { NONE = 0, READ = 1 << 0, @@ -24,7 +25,7 @@ class ASocket }; ASocket() = delete; - explicit ASocket(int fd, IOState state = IOState::READ); + explicit ASocket(int fd, IoState state = IoState::NONE); ASocket(const ASocket &other) = delete; ASocket &operator=(const ASocket &other) = delete; @@ -35,7 +36,7 @@ class ASocket [[nodiscard]] virtual Type getType() const noexcept = 0; [[nodiscard]] int getFd() const noexcept; - [[nodiscard]] IOState getEvent() const noexcept; + [[nodiscard]] IoState getEvent() const noexcept; [[nodiscard]] bool isDirty() const noexcept; void callback() const; @@ -44,7 +45,7 @@ class ASocket virtual ssize_t read(void *buf, size_t len) const; virtual ssize_t write(const void *buf, size_t len) const; - void setIOState(IOState event); + void setIOState(IoState event); void processed(); protected: @@ -54,6 +55,6 @@ class ASocket private: int fd_; bool dirty_ = false; - IOState ioState_; + IoState ioState_; std::function callback_ = nullptr; }; diff --git a/webserv/socket/CgiSocket.cpp b/webserv/socket/CgiSocket.cpp index aac5868..73a2dd8 100644 --- a/webserv/socket/CgiSocket.cpp +++ b/webserv/socket/CgiSocket.cpp @@ -4,7 +4,7 @@ #include -CgiSocket::CgiSocket(int fd, ASocket::IOState event) : ASocket(fd, event) +CgiSocket::CgiSocket(int fd, ASocket::IoState event) : ASocket(fd, event) { Log::trace(LOCATION); } diff --git a/webserv/socket/CgiSocket.hpp b/webserv/socket/CgiSocket.hpp index 42c3235..4af4ab3 100644 --- a/webserv/socket/CgiSocket.hpp +++ b/webserv/socket/CgiSocket.hpp @@ -9,7 +9,7 @@ class CgiSocket : public ASocket { public: - explicit CgiSocket(int fd, ASocket::IOState event); + explicit CgiSocket(int fd, ASocket::IoState event); [[nodiscard]] ASocket::Type getType() const noexcept override; diff --git a/webserv/socket/ServerSocket.cpp b/webserv/socket/ServerSocket.cpp index f6e9119..77a54e9 100644 --- a/webserv/socket/ServerSocket.cpp +++ b/webserv/socket/ServerSocket.cpp @@ -11,7 +11,7 @@ #include // for AF_INET, accept, bind, listen, setsockopt, socket, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR #include // for close -ServerSocket::ServerSocket() : ASocket(socket(AF_INET, SOCK_STREAM, 0)) +ServerSocket::ServerSocket() : ASocket(socket(AF_INET, SOCK_STREAM, 0), ASocket::IoState::READ) { Log::trace(LOCATION); if (getFd() == -1) diff --git a/webserv/socket/TimerSocket.cpp b/webserv/socket/TimerSocket.cpp new file mode 100644 index 0000000..bd86811 --- /dev/null +++ b/webserv/socket/TimerSocket.cpp @@ -0,0 +1,46 @@ +#include "webserv/log/Log.hpp" +#include "webserv/socket/ASocket.hpp" + +#include + +#include + +#include +#include + +TimerSocket::TimerSocket(std::chrono::milliseconds timeout) + : ASocket(timerfd_create(CLOCK_MONOTONIC, 0), ASocket::IoState::NONE), timeout_(timeout) +{ +} + +void TimerSocket::activate() +{ + Log::trace(LOCATION); + struct itimerspec timerSpec; + + auto seconds = std::chrono::duration_cast(timeout_); + auto nanoseconds = std::chrono::duration_cast(timeout_ - seconds); + + timerSpec.it_value.tv_sec = seconds.count(); + timerSpec.it_value.tv_nsec = nanoseconds.count(); + timerSpec.it_interval.tv_sec = 0; + timerSpec.it_interval.tv_nsec = 0; + + if (timerfd_settime(getFd(), 0, &timerSpec, nullptr) == -1) + { + throw std::runtime_error("Failed to set timerfd time"); + } + + active_ = true; + setIOState(IoState::READ); +} + +ASocket::Type TimerSocket::getType() const noexcept +{ + return ASocket::Type::TIMER_SOCKET; +} + +bool TimerSocket::isActive() const noexcept +{ + return active_; +} \ No newline at end of file diff --git a/webserv/socket/TimerSocket.hpp b/webserv/socket/TimerSocket.hpp new file mode 100644 index 0000000..2042484 --- /dev/null +++ b/webserv/socket/TimerSocket.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "webserv/socket/ASocket.hpp" +#include + +class TimerSocket : public ASocket +{ + public: + explicit TimerSocket(std::chrono::milliseconds timeout); + + [[nodiscard]] ASocket::Type getType() const noexcept override; + [[nodiscard]] bool isActive() const noexcept; + + void activate(); + private: + bool active_ = false; + std::chrono::milliseconds timeout_; +}; \ No newline at end of file diff --git a/webserv/utils/utils.cpp b/webserv/utils/utils.cpp index df30635..b28bd09 100644 --- a/webserv/utils/utils.cpp +++ b/webserv/utils/utils.cpp @@ -139,15 +139,15 @@ std::string implode(const std::vector &elements, const std::string return stream.str(); } -uint32_t stateToEpoll(const ASocket::IOState &event) +uint32_t stateToEpoll(const ASocket::IoState &event) { uint32_t epollEvents = 0; - using EventType = std::underlying_type_t; - if ((static_cast(event) & static_cast(ASocket::IOState::READ)) != 0U) + using EventType = std::underlying_type_t; + if ((static_cast(event) & static_cast(ASocket::IoState::READ)) != 0U) { epollEvents |= EPOLLIN; } - if ((static_cast(event) & static_cast(ASocket::IOState::WRITE)) != 0U) + if ((static_cast(event) & static_cast(ASocket::IoState::WRITE)) != 0U) { epollEvents |= EPOLLOUT; } diff --git a/webserv/utils/utils.hpp b/webserv/utils/utils.hpp index 1e305b1..01c312b 100644 --- a/webserv/utils/utils.hpp +++ b/webserv/utils/utils.hpp @@ -18,5 +18,5 @@ void removeComments(std::string &str); std::vector split(const std::string &str, char delimiter); std::string implode(const std::vector &elements, const std::string &delimiter); -uint32_t stateToEpoll(const ASocket::IOState &event); +uint32_t stateToEpoll(const ASocket::IoState &event); } // namespace utils \ No newline at end of file