timeout, but please refactor yes thankyou

This commit is contained in:
Quinten 2025-10-21 22:17:17 +02:00
parent c148636b1e
commit e7e913a32d
20 changed files with 177 additions and 39 deletions

View File

@ -13,6 +13,8 @@ if (!isset($_SESSION['request_count'])) {
// Increment on each request // Increment on each request
$_SESSION['request_count']++; $_SESSION['request_count']++;
sleep(8); // Simulate some processing delay
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">

View File

@ -93,7 +93,7 @@ void Client::request()
} }
else else
{ {
ErrorHandler::createErrorResponse(500, *httpResponse_); ErrorHandler::createErrorResponse(500, *httpResponse_);
} }
} }
else 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())); Log::info("Response is ready to be sent to client, fd: " + std::to_string(clientSocket_->getFd()));
clientSocket_->setCallback([this]() { respond(); }); clientSocket_->setCallback([this]() { respond(); });
// server_.writable(clientSocket_->getFd()); // server_.writable(clientSocket_->getFd());
clientSocket_->setIOState(ASocket::IOState::WRITE); clientSocket_->setIOState(ASocket::IoState::WRITE);
} }
} }

View File

@ -1,7 +1,31 @@
#include "webserv/log/Log.hpp"
#include <webserv/handler/AHandler.hpp> #include <webserv/handler/AHandler.hpp>
#include <webserv/http/HttpRequest.hpp> #include <webserv/http/HttpRequest.hpp>
#include <webserv/http/HttpResponse.hpp> #include <webserv/http/HttpResponse.hpp>
#include <webserv/client/Client.hpp>
#include <memory>
#include <chrono>
#include <string>
AHandler::AHandler(const HttpRequest &request, HttpResponse &response) : request_(request), response_(response) {} AHandler::AHandler(const HttpRequest &request, HttpResponse &response) : request_(request), response_(response) {}
void AHandler::startTimer()
{
timerSocket_ = std::make_unique<TimerSocket>(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;
}
}

View File

@ -1,26 +1,31 @@
#pragma once #pragma once
#include "webserv/socket/TimerSocket.hpp"
#include <memory>
class HttpRequest; class HttpRequest;
class HttpResponse; class HttpResponse;
class AHandler class AHandler
{ {
public: public:
AHandler(const HttpRequest &request, HttpResponse &response); AHandler(const HttpRequest &request, HttpResponse &response);
virtual ~AHandler() = default; virtual ~AHandler() = default;
AHandler(const AHandler &other) = delete; AHandler(const AHandler &other) = delete;
AHandler &operator=(const AHandler &other) = delete; AHandler &operator=(const AHandler &other) = delete;
AHandler(AHandler &&other) noexcept = delete; AHandler(AHandler &&other) noexcept = delete;
AHandler &operator=(AHandler &&other) noexcept = delete; AHandler &operator=(AHandler &&other) noexcept = delete;
virtual void handle() = 0; 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> timerSocket_;
}; };

View File

@ -1,3 +1,4 @@
#include "webserv/handler/ErrorHandler.hpp"
#include <webserv/client/Client.hpp> // for Client #include <webserv/client/Client.hpp> // for Client
#include <webserv/handler/CgiHandler.hpp> #include <webserv/handler/CgiHandler.hpp>
#include <webserv/handler/CgiProcess.hpp> // for CgiProcess #include <webserv/handler/CgiProcess.hpp> // for CgiProcess
@ -20,6 +21,8 @@ void CgiHandler::handle()
// Initialize CGI process // Initialize CGI process
cgiProcess_ = std::make_unique<CgiProcess>(request_, *this); cgiProcess_ = std::make_unique<CgiProcess>(request_, *this);
startTimer();
Log::info("CGI process started and sockets registered"); 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())); Log::info("CGI process closed stdout, fd: " + std::to_string(cgiStdOut_->getFd()));
request_.getClient().removeSocket(cgiStdOut_.get()); request_.getClient().removeSocket(cgiStdOut_.get());
request_.getClient().removeSocket(timerSocket_.get());
cgiStdOut_ = nullptr; cgiStdOut_ = nullptr;
parseCgiOutput(); parseCgiOutput();
return; return;
@ -103,6 +107,7 @@ void CgiHandler::error()
{ {
Log::info("CGI process closed stderr, fd: " + std::to_string(cgiStdErr_->getFd())); Log::info("CGI process closed stderr, fd: " + std::to_string(cgiStdErr_->getFd()));
request_.getClient().removeSocket(cgiStdErr_.get()); request_.getClient().removeSocket(cgiStdErr_.get());
request_.getClient().removeSocket(timerSocket_.get()); // todo maybe this dangerous
cgiStdErr_ = nullptr; cgiStdErr_ = nullptr;
return; 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() void CgiHandler::parseCgiBody()
{ {
Log::trace(LOCATION); Log::trace(LOCATION);

View File

@ -24,6 +24,9 @@ class CgiHandler : public AHandler
void setCgiSockets(std::unique_ptr<CgiSocket> cgiStdIn, std::unique_ptr<CgiSocket> cgiStdOut, std::unique_ptr<CgiSocket> cgiStdErr); void setCgiSockets(std::unique_ptr<CgiSocket> cgiStdIn, std::unique_ptr<CgiSocket> cgiStdOut, std::unique_ptr<CgiSocket> cgiStdErr);
void setPid(int pid); void setPid(int pid);
protected:
void handleTimeout() override;
private: private:
constexpr static size_t bufferSize_ = 8192; // TODO: remove duplicate definition and move to configmanager constexpr static size_t bufferSize_ = 8192; // TODO: remove duplicate definition and move to configmanager
std::vector<uint8_t> buffer_; std::vector<uint8_t> buffer_;

View File

@ -82,9 +82,9 @@ void CgiProcess::spawn()
else else
{ {
// Parent process // Parent process
auto cgiStdIn = std::make_unique<CgiSocket>(pipeStdin[1], ASocket::IOState::WRITE); auto cgiStdIn = std::make_unique<CgiSocket>(pipeStdin[1], ASocket::IoState::WRITE);
auto cgiStdOut = std::make_unique<CgiSocket>(pipeStdout[0], ASocket::IOState::READ); auto cgiStdOut = std::make_unique<CgiSocket>(pipeStdout[0], ASocket::IoState::READ);
auto cgiStdErr = std::make_unique<CgiSocket>(pipeStderr[0], ASocket::IOState::READ); auto cgiStdErr = std::make_unique<CgiSocket>(pipeStderr[0], ASocket::IoState::READ);
close(pipeStdin[0]); close(pipeStdin[0]);
close(pipeStdout[1]); close(pipeStdout[1]);
@ -95,7 +95,7 @@ void CgiProcess::spawn()
// request_.getClient().setCgiSockets(std::move(cgiStdIn), std::move(cgiStdOut)); // move the sockets to the // request_.getClient().setCgiSockets(std::move(cgiStdIn), std::move(cgiStdOut)); // move the sockets to the
// client // client
handler_.setCgiSockets(std::move(cgiStdIn), std::move(cgiStdOut), std::move(cgiStdErr)); handler_.setCgiSockets(std::move(cgiStdIn), std::move(cgiStdOut), std::move(cgiStdErr));
handler_.setPid(_pid); handler_.setPid(_pid);
} }
} }

View File

@ -20,6 +20,12 @@ FileHandler::FileHandler(const HttpRequest &request, HttpResponse &response)
Log::trace(LOCATION); 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 void FileHandler::handleFile(const std::string &filepath) const
{ {
Log::trace(LOCATION); Log::trace(LOCATION);

View File

@ -29,6 +29,9 @@ class FileHandler : public AHandler
void handle() override; void handle() override;
protected:
void handleTimeout() override;
private: private:
const URI &uri_; const URI &uri_;
const AConfig *config_; const AConfig *config_;

View File

@ -49,7 +49,7 @@ class Log
void log(Level level, const std::string &message, const std::map<std::string, std::string> &context); void log(Level level, const std::string &message, const std::map<std::string, std::string> &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 setFileChannel(const std::string &filename, std::ios_base::openmode mode = std::ios_base::app);
static void setStdoutChannel(); static void setStdoutChannel();

View File

@ -89,7 +89,12 @@ void Server::remove(ASocket &socket)
int fd = socket.getFd(); int fd = socket.getFd();
if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr) == -1) 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"); throw std::runtime_error("epoll_ctl DEL failed");
} }
socketToClient_.erase(fd); socketToClient_.erase(fd);
@ -133,7 +138,7 @@ void Server::handleConnection(struct epoll_event *event)
Log::trace(LOCATION); Log::trace(LOCATION);
ServerSocket &listener = getListener(event->data.fd); ServerSocket &listener = getListener(event->data.fd);
std::unique_ptr<ClientSocket> clientSocket = listener.accept(); std::unique_ptr<ClientSocket> clientSocket = listener.accept();
clientSocket->setIOState(ASocket::IoState::READ);
auto client = std::make_unique<Client>(std::move(clientSocket), *this); auto client = std::make_unique<Client>(std::move(clientSocket), *this);
add(client->getSocket(), client.get()); add(client->getSocket(), client.get());
clients_.emplace_back(std::move(client)); clients_.emplace_back(std::move(client));
@ -198,7 +203,12 @@ void Server::update(const ASocket &socket) const
evt.data.fd = socketFd; evt.data.fd = socketFd;
if (epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, socketFd, &evt) == -1) 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"); throw std::runtime_error("epoll_ctl MOD failed");
} }
} }

View File

@ -10,7 +10,7 @@
#include <sys/socket.h> // for recv, send #include <sys/socket.h> // for recv, send
#include <unistd.h> // for close #include <unistd.h> // 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); Log::trace(LOCATION);
if (fd_ == -1) if (fd_ == -1)
@ -65,7 +65,7 @@ int ASocket::getFd() const noexcept
return fd_; return fd_;
} }
ASocket::IOState ASocket::getEvent() const noexcept ASocket::IoState ASocket::getEvent() const noexcept
{ {
return ioState_; return ioState_;
} }
@ -75,7 +75,7 @@ bool ASocket::isDirty() const noexcept
return dirty_; return dirty_;
} }
void ASocket::setIOState(IOState event) void ASocket::setIOState(IoState event)
{ {
if (event == ioState_) if (event == ioState_)
{ {

View File

@ -13,10 +13,11 @@ class ASocket
{ {
CLIENT_SOCKET, CLIENT_SOCKET,
SERVER_SOCKET, SERVER_SOCKET,
CGI_SOCKET CGI_SOCKET,
TIMER_SOCKET
}; };
enum class IOState : uint32_t enum class IoState : uint32_t
{ {
NONE = 0, NONE = 0,
READ = 1 << 0, READ = 1 << 0,
@ -24,7 +25,7 @@ class ASocket
}; };
ASocket() = delete; ASocket() = delete;
explicit ASocket(int fd, IOState state = IOState::READ); explicit ASocket(int fd, IoState state = IoState::NONE);
ASocket(const ASocket &other) = delete; ASocket(const ASocket &other) = delete;
ASocket &operator=(const ASocket &other) = delete; ASocket &operator=(const ASocket &other) = delete;
@ -35,7 +36,7 @@ class ASocket
[[nodiscard]] virtual Type getType() const noexcept = 0; [[nodiscard]] virtual Type getType() const noexcept = 0;
[[nodiscard]] int getFd() const noexcept; [[nodiscard]] int getFd() const noexcept;
[[nodiscard]] IOState getEvent() const noexcept; [[nodiscard]] IoState getEvent() const noexcept;
[[nodiscard]] bool isDirty() const noexcept; [[nodiscard]] bool isDirty() const noexcept;
void callback() const; void callback() const;
@ -44,7 +45,7 @@ class ASocket
virtual ssize_t read(void *buf, size_t len) const; virtual ssize_t read(void *buf, size_t len) const;
virtual ssize_t write(const 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(); void processed();
protected: protected:
@ -54,6 +55,6 @@ class ASocket
private: private:
int fd_; int fd_;
bool dirty_ = false; bool dirty_ = false;
IOState ioState_; IoState ioState_;
std::function<void()> callback_ = nullptr; std::function<void()> callback_ = nullptr;
}; };

View File

@ -4,7 +4,7 @@
#include <unistd.h> #include <unistd.h>
CgiSocket::CgiSocket(int fd, ASocket::IOState event) : ASocket(fd, event) CgiSocket::CgiSocket(int fd, ASocket::IoState event) : ASocket(fd, event)
{ {
Log::trace(LOCATION); Log::trace(LOCATION);
} }

View File

@ -9,7 +9,7 @@
class CgiSocket : public ASocket class CgiSocket : public ASocket
{ {
public: public:
explicit CgiSocket(int fd, ASocket::IOState event); explicit CgiSocket(int fd, ASocket::IoState event);
[[nodiscard]] ASocket::Type getType() const noexcept override; [[nodiscard]] ASocket::Type getType() const noexcept override;

View File

@ -11,7 +11,7 @@
#include <sys/socket.h> // for AF_INET, accept, bind, listen, setsockopt, socket, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR #include <sys/socket.h> // for AF_INET, accept, bind, listen, setsockopt, socket, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
#include <unistd.h> // for close #include <unistd.h> // 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); Log::trace(LOCATION);
if (getFd() == -1) if (getFd() == -1)

View File

@ -0,0 +1,46 @@
#include "webserv/log/Log.hpp"
#include "webserv/socket/ASocket.hpp"
#include <webserv/socket/TimerSocket.hpp>
#include <stdexcept>
#include <sys/time.h>
#include <sys/timerfd.h>
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<std::chrono::seconds>(timeout_);
auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(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_;
}

View File

@ -0,0 +1,18 @@
#pragma once
#include "webserv/socket/ASocket.hpp"
#include <chrono>
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_;
};

View File

@ -139,15 +139,15 @@ std::string implode(const std::vector<std::string> &elements, const std::string
return stream.str(); return stream.str();
} }
uint32_t stateToEpoll(const ASocket::IOState &event) uint32_t stateToEpoll(const ASocket::IoState &event)
{ {
uint32_t epollEvents = 0; uint32_t epollEvents = 0;
using EventType = std::underlying_type_t<ASocket::IOState>; using EventType = std::underlying_type_t<ASocket::IoState>;
if ((static_cast<EventType>(event) & static_cast<EventType>(ASocket::IOState::READ)) != 0U) if ((static_cast<EventType>(event) & static_cast<EventType>(ASocket::IoState::READ)) != 0U)
{ {
epollEvents |= EPOLLIN; epollEvents |= EPOLLIN;
} }
if ((static_cast<EventType>(event) & static_cast<EventType>(ASocket::IOState::WRITE)) != 0U) if ((static_cast<EventType>(event) & static_cast<EventType>(ASocket::IoState::WRITE)) != 0U)
{ {
epollEvents |= EPOLLOUT; epollEvents |= EPOLLOUT;
} }

View File

@ -18,5 +18,5 @@ void removeComments(std::string &str);
std::vector<std::string> split(const std::string &str, char delimiter); std::vector<std::string> split(const std::string &str, char delimiter);
std::string implode(const std::vector<std::string> &elements, const std::string &delimiter); std::string implode(const std::vector<std::string> &elements, const std::string &delimiter);
uint32_t stateToEpoll(const ASocket::IOState &event); uint32_t stateToEpoll(const ASocket::IoState &event);
} // namespace utils } // namespace utils