Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions example/cgi/timeout.cgi
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

# HTTP Header
echo "Content-Type: text/html"
sleep 10
echo ""

# HTML Content
echo "<html>"
echo "<head><title>CGI Test</title></head>"
echo "<body>"
echo "<h1>Hello, CGI!</h1>"
echo "<p>This page was generated by a Shell script.</p>"
echo "</body>"
echo "</html>"
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ add_library(webserv_lib STATIC
lib/utils/logger.hpp
lib/utils/string.cpp
lib/utils/string.hpp
lib/utils/time.cpp
lib/utils/time.hpp
lib/utils/types/option.hpp
lib/utils/types/result.hpp
lib/utils/types/unit.hpp
Expand Down
4 changes: 3 additions & 1 deletion src/lib/core/action/run_cgi_action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "core/handler/write_cgi_request_handler.hpp"
#include "../../cgi/meta_variable.hpp"
#include "utils/fd.hpp"
#include "utils/time.hpp"
#include "utils/logger.hpp"

void RunCgiAction::execute(ActionContext &ctx) {
Expand Down Expand Up @@ -134,5 +135,6 @@ void RunCgiAction::parentRoutine(const ActionContext &ctx, const int socketFd, c
ctx.getState().getEventNotifier().unregisterEvent(Event(clientFd_, Event::kWrite));
ctx.getState().getEventHandlerRepository().remove(clientFd_, Event::kRead);
ctx.getState().getEventHandlerRepository().remove(clientFd_, Event::kWrite);
ctx.getState().getCgiProcessRepository().set(childPid, {clientFd_, socketFd});
CgiProcessRepository::Data data = {clientFd_, socketFd, utils::Time::getCurrentTime()};
ctx.getState().getCgiProcessRepository().set(childPid, data);
}
3 changes: 3 additions & 0 deletions src/lib/core/handler/read_request_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ IEventHandler::InvokeResult ReadRequestHandler::invoke(const Context &ctx) {
LOG_DEBUG("start ReadRequestHandler");
const auto conn = ctx.getConnection().unwrap();

// アクティビティを更新
ctx.getConnection().unwrap().get().updateActivity();

ReadBuffer &readBuf = conn.get().getReadBuffer();
const Result<Option<http::Request>, error::AppError> result = reqReader_.readRequest(readBuf);
if (result.isErr()) {
Expand Down
83 changes: 82 additions & 1 deletion src/lib/core/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
#include "http/response/response_builder.hpp"
#include "transport/listener.hpp"
#include "utils/logger.hpp"
#include "utils/time.hpp"
#include <map>
#include <signal.h>
#include <cstring>

Server::Server(const config::Config &config) : config_(config) {
const config::ServerContextList &servers = config_.getServers();
Expand Down Expand Up @@ -74,8 +77,12 @@ void Server::start() {
if (!result.empty()) LOG_DEBUG("child process reaped");
for (std::vector<ChildReaper::ReapedProcess>::const_iterator it = result.begin(); it != result.end();
++it) {
if (it->status == 0) continue;
const Option<CgiProcessRepository::Data> data = state_.getCgiProcessRepository().get(it->pid);
state_.getCgiProcessRepository().remove(it->pid);

if (it->status == 0) continue;

// CGI スクリプトがエラーで終わった場合
if (data.isNone()) {
LOG_WARNF(
"reaped child process %d with non-zero status %d, but no client fd found",
Expand Down Expand Up @@ -109,6 +116,9 @@ void Server::start() {

invokeHandlers(ctx);
}

// タイムアウトチェック
removeTimeoutHandlers();
}
}

Expand Down Expand Up @@ -210,3 +220,74 @@ void Server::executeActions(ActionContext &actionCtx, std::vector<IAction *> act
delete action;
}
}

void Server::removeTimeoutHandlers() {
removeTimeoutRequestHandlers();
removeTimeoutCgiProcesses();
}

void Server::removeTimeoutRequestHandlers() {
const std::time_t currentTime = utils::Time::getCurrentTime();

const std::vector<int> timedOutFds =
state_.getConnectionRepository().getTimedOutConnectionFds(currentTime, REQUEST_TIMEOUT_SECONDS, listenerFds_);

for (std::vector<int>::const_iterator it = timedOutFds.begin(); it != timedOutFds.end(); ++it) {
const int fd = *it;

// Read ハンドラーが登録されているかチェック(リクエスト待ち状態)
const Option<Ref<IEventHandler> > readHandler = state_.getEventHandlerRepository().get(fd, Event::kRead);
if (readHandler.isNone()) {
continue;
}

LOG_INFOF("Request timeout for fd %d", fd);

// タイムアウトレスポンスを設定
http::ResponseBuilder builder;
http::Response response = builder.status(http::kStatusRequestTimeout).build();

// Read ハンドラーを削除し、Write ハンドラーを設定
state_.getEventNotifier().unregisterEvent(Event(fd, Event::kRead));
state_.getEventHandlerRepository().remove(fd, Event::kRead);
state_.getEventNotifier().registerEvent(Event(fd, Event::kWrite));
state_.getEventHandlerRepository().set(fd, Event::kWrite, new WriteResponseHandler(response));
}
}

void Server::removeTimeoutCgiProcesses() {
const std::time_t currentTime = utils::Time::getCurrentTime();
const std::vector<std::pair<pid_t, CgiProcessRepository::Data> > timedOutProcesses =
state_.getCgiProcessRepository().getTimedOutProcesses(currentTime, CGI_TIMEOUT_SECONDS);

for (std::vector<std::pair<pid_t, CgiProcessRepository::Data> >::const_iterator it = timedOutProcesses.begin();
it != timedOutProcesses.end();
++it) {
const pid_t pid = it->first;
const CgiProcessRepository::Data &data = it->second;

LOG_INFOF("CGI timeout for pid %d", pid);

// CGI プロセスを強制終了
if (kill(pid, SIGTERM) == -1) {
LOG_WARNF("Failed to terminate CGI process %d: %s", pid, std::strerror(errno));
}

// プロセスソケットをクリーンアップ
const int processSocketFd = data.processSocketFd;
const int clientFd = data.clientFd;

state_.getEventNotifier().unregisterEvent(Event(processSocketFd, Event::kRead));
state_.getEventNotifier().unregisterEvent(Event(processSocketFd, Event::kWrite));
state_.getEventHandlerRepository().remove(processSocketFd, Event::kRead);
state_.getEventHandlerRepository().remove(processSocketFd, Event::kWrite);
state_.getConnectionRepository().remove(processSocketFd);
state_.getCgiProcessRepository().remove(pid);

// Gateway Timeout レスポンスを返す
http::ResponseBuilder builder;
http::Response response = builder.status(http::kStatusGatewayTimeout).build();
state_.getEventNotifier().registerEvent(Event(clientFd, Event::kWrite));
state_.getEventHandlerRepository().set(clientFd, Event::kWrite, new WriteResponseHandler(response));
}
}
6 changes: 6 additions & 0 deletions src/lib/core/server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class Server {
// VirtualServer は http::Router を持っていて、コピー不可なのでポインタで持つ
typedef std::vector<VirtualServer *> VirtualServerList;

static const int REQUEST_TIMEOUT_SECONDS = 5;
static const int CGI_TIMEOUT_SECONDS = 5;

config::Config config_;
VirtualServerList virtualServers_;

Expand All @@ -32,6 +35,9 @@ class Server {
static void executeActions(ActionContext &actionCtx, std::vector<IAction *> actions);
void invokeHandlers(const Context &ctx);
void invokeSingleHandler(const Context &ctx, const Ref<IEventHandler> &handler, bool shouldCallHandler);
void removeTimeoutHandlers();
void removeTimeoutRequestHandlers();
void removeTimeoutCgiProcesses();
};

#endif
44 changes: 44 additions & 0 deletions src/lib/core/server_state.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include "server_state.hpp"
#include "utils/logger.hpp"
#include "utils/ref.hpp"
#include "utils/time.hpp"
#include <vector>
#include <set>

ConnectionRepository::ConnectionRepository() {}

Expand Down Expand Up @@ -45,6 +48,30 @@ void ConnectionRepository::remove(const int fd) {
LOG_DEBUGF("connection removed from server");
}

std::vector<int> ConnectionRepository::getTimedOutConnectionFds(
std::time_t currentTime, double timeoutSeconds, const std::set<int> &excludeFds
) const {
std::vector<int> timedOutFds;

for (std::map<int, Connection *>::const_iterator it = connections_.begin(); it != connections_.end(); ++it) {
const int fd = it->first;
Connection *conn = it->second;

// 除外リストに含まれるFDはスキップ
if (excludeFds.count(fd) > 0) {
continue;
}

// タイムアウトチェック
const double elapsed = utils::Time::diffTimeSeconds(currentTime, conn->getLastActivityTime());
if (elapsed > timeoutSeconds) {
timedOutFds.push_back(fd);
}
}

return timedOutFds;
}

EventHandlerRepository::EventHandlerRepository() {}

EventHandlerRepository::~EventHandlerRepository() {
Expand Down Expand Up @@ -104,6 +131,23 @@ void CgiProcessRepository::remove(const pid_t pid) {
pidToData_.erase(pid);
}

std::vector<std::pair<pid_t, CgiProcessRepository::Data> >
CgiProcessRepository::getTimedOutProcesses(std::time_t currentTime, double timeoutSeconds) const {
std::vector<std::pair<pid_t, CgiProcessRepository::Data> > timedOutProcesses;

for (std::map<pid_t, Data>::const_iterator it = pidToData_.begin(); it != pidToData_.end(); ++it) {
const pid_t pid = it->first;
const Data &data = it->second;

const double elapsed = utils::Time::diffTimeSeconds(currentTime, data.startTime);
if (elapsed > timeoutSeconds) {
timedOutProcesses.push_back(std::make_pair(pid, data));
}
}

return timedOutProcesses;
}

ServerState::ServerState() {
// self-pipe の読み端を監視対象にする
reaper_.attachToEventNotifier(&getEventNotifier());
Expand Down
11 changes: 11 additions & 0 deletions src/lib/core/server_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#include "transport/connection.hpp"
#include "utils/types/option.hpp"
#include <map>
#include <set>
#include <vector>
#include <ctime>

// TODO: 共通化するべき?

Expand All @@ -23,6 +26,10 @@ class ConnectionRepository : public NonCopyable {
void set(int fd, Connection *conn);
void remove(int fd);

// タイムアウトしたコネクションのFDを取得
std::vector<int>
getTimedOutConnectionFds(std::time_t currentTime, double timeoutSeconds, const std::set<int> &excludeFds) const;

private:
std::map<int, Connection *> connections_;
};
Expand All @@ -48,6 +55,7 @@ class CgiProcessRepository : public NonCopyable {
struct Data {
int clientFd;
int processSocketFd;
std::time_t startTime;
};

CgiProcessRepository() {}
Expand All @@ -56,6 +64,9 @@ class CgiProcessRepository : public NonCopyable {
void set(pid_t pid, Data data);
void remove(pid_t pid);

// タイムアウトしたプロセスを取得
std::vector<std::pair<pid_t, Data> > getTimedOutProcesses(std::time_t currentTime, double timeoutSeconds) const;

private:
std::map<pid_t, Data> pidToData_;
};
Expand Down
8 changes: 4 additions & 4 deletions src/lib/event/event_notifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ void EpollEventNotifier::unregisterEvent(const Event &event) {
}
}

EpollEventNotifier::WaitEventsResult EpollEventNotifier::waitEvents() {
EpollEventNotifier::WaitEventsResult EpollEventNotifier::waitEvents(int timeoutMs) {
epoll_event evs[1024];
const int numEvents = epoll_wait(epollFd_.get(), evs, 1024, -1);
const int numEvents = epoll_wait(epollFd_.get(), evs, 1024, timeoutMs);
if (numEvents == -1) {
LOG_ERRORF("epoll_wait failed: %s", std::strerror(errno));
return Err(error::kUnknown);
Expand Down Expand Up @@ -160,7 +160,7 @@ void PollEventNotifier::unregisterEvent(const Event &event) {
}
}

IEventNotifier::WaitEventsResult PollEventNotifier::waitEvents() {
IEventNotifier::WaitEventsResult PollEventNotifier::waitEvents(int timeoutMs) {
std::vector<pollfd> fds;
for (EventMap::const_iterator it = registeredEvents_.begin(); it != registeredEvents_.end(); ++it) {
pollfd pfd = {};
Expand All @@ -169,7 +169,7 @@ IEventNotifier::WaitEventsResult PollEventNotifier::waitEvents() {
fds.push_back(pfd);
}

const int result = poll(fds.data(), fds.size(), -1);
const int result = poll(fds.data(), fds.size(), timeoutMs);
if (result == -1) {
LOG_ERRORF("poll failed: %s", std::strerror(errno));
return Err(error::kUnknown);
Expand Down
6 changes: 3 additions & 3 deletions src/lib/event/event_notifier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class IEventNotifier {
virtual void unregisterEvent(const Event &event) = 0;

typedef Result<std::vector<Event>, error::AppError> WaitEventsResult;
virtual WaitEventsResult waitEvents() = 0;
virtual WaitEventsResult waitEvents(int timeoutMs = 1000) = 0;
};

// epoll の抽象
Expand All @@ -29,7 +29,7 @@ class EpollEventNotifier : public IEventNotifier {

void registerEvent(const Event &event);
void unregisterEvent(const Event &event);
WaitEventsResult waitEvents();
WaitEventsResult waitEvents(int timeoutMs = 1000);

private:
AutoFd epollFd_;
Expand All @@ -46,7 +46,7 @@ class PollEventNotifier : public IEventNotifier {

void registerEvent(const Event &event);
void unregisterEvent(const Event &event);
WaitEventsResult waitEvents();
WaitEventsResult waitEvents(int timeoutMs = 1000);

private:
typedef std::map<int, Event> EventMap;
Expand Down
11 changes: 10 additions & 1 deletion src/lib/transport/connection.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#include "connection.hpp"
#include "utils/logger.hpp"
#include "utils/time.hpp"

Connection::Connection(const int fd, const Address &localAddress, const Address &foreignAddress)
: clientFd_(fd), localAddress_(localAddress), foreignAddress_(foreignAddress), fdReader_(clientFd_),
buffer_(fdReader_) {}
buffer_(fdReader_), lastActivityTime_(utils::Time::getCurrentTime()) {}

Connection::~Connection() {
LOG_DEBUG("Connection: destruct");
Expand All @@ -24,3 +25,11 @@ const Address &Connection::getForeignAddress() const {
ReadBuffer &Connection::getReadBuffer() {
return buffer_;
}

std::time_t Connection::getLastActivityTime() const {
return lastActivityTime_;
}

void Connection::updateActivity() {
lastActivityTime_ = utils::Time::getCurrentTime();
}
Loading
Loading