feat: CGI

This commit is contained in:
Quinten 2025-10-15 18:34:42 +02:00
parent e1be8d8aa8
commit d562c5bcbf
9 changed files with 174 additions and 58 deletions

View File

@ -1,3 +1,3 @@
<?php
echo "Hello, World! This is site-3.";
phpinfo();

View File

@ -21,18 +21,18 @@
Client::Client(std::unique_ptr<ClientSocket> socket, Server &server)
: httpRequest_(std::make_unique<HttpRequest>(this)), httpResponse_(std::make_unique<HttpResponse>()),
router_(std::make_unique<Router>(this)), client_socket_(std::move(socket)), server_(std::ref(server))
router_(std::make_unique<Router>(this)), clientSocket_(std::move(socket)), server_(std::ref(server))
{
Log::trace(LOCATION);
Log::info("New client connected, fd: " + std::to_string(client_socket_->getFd()));
client_socket_->setCallback([this]() { request(); });
Log::info("New client connected, fd: " + std::to_string(clientSocket_->getFd()));
clientSocket_->setCallback([this]() { request(); });
}
Client::~Client()
{
Log::trace(LOCATION);
Log::info("Client disconnected, fd: " + std::to_string(client_socket_->getFd()));
server_.remove(*client_socket_);
Log::info("Client disconnected, fd: " + std::to_string(clientSocket_->getFd()));
server_.remove(*clientSocket_);
};
int Client::getStatusCode() const
@ -47,13 +47,17 @@ void Client::setStatusCode(int code)
ASocket &Client::getSocket(int fd) const
{
if (fd == -1 || client_socket_->getFd() == fd)
if (fd == -1 || clientSocket_->getFd() == fd)
{
return *client_socket_;
return *clientSocket_;
}
if (cgi_socket_ && cgi_socket_->getFd() == fd)
if (cgiStdIn_ && cgiStdIn_->getFd() == fd)
{
return *client_socket_; // TODO return cgi socket
return *cgiStdIn_;
}
if (cgiStdOut_ && cgiStdOut_->getFd() == fd)
{
return *cgiStdOut_;
}
Log::error("Socket not found for fd: " + std::to_string(fd));
throw std::runtime_error("Socket not found for fd: " + std::to_string(fd));
@ -63,7 +67,7 @@ void Client::request()
{
Log::trace(LOCATION);
char buffer[bufferSize_] = {}; // NOLINT(cppcoreguidelines-avoid-c-arrays)
ssize_t bytesRead = client_socket_->read(
ssize_t bytesRead = clientSocket_->read(
buffer, sizeof(buffer) - 1); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
if (bytesRead < 0)
{
@ -72,7 +76,7 @@ void Client::request()
}
if (bytesRead == 0)
{
Log::info("Client closed connection, fd: " + std::to_string(client_socket_->getFd()));
Log::info("Client closed connection, fd: " + std::to_string(clientSocket_->getFd()));
server_.disconnect(*this); // CRITICAL: RETURN IMMEDIATELY
return;
}
@ -105,10 +109,76 @@ void Client::request()
}
}
void Client::setCgiSocket(std::unique_ptr<CgiSocket> cgiSocket)
void Client::writeToCgi()
{
server_.add(*cgiSocket, EPOLLIN, this);
cgi_socket_ = std::move(cgiSocket);
Log::trace(LOCATION);
if (cgiStdIn_ == nullptr)
{
Log::error("CGI stdin socket is null");
return;
}
if (httpRequest_->getBody().empty())
{
Log::debug("No body to write to CGI stdin, fd: " + std::to_string(cgiStdIn_->getFd()));
server_.remove(*cgiStdIn_);
cgiStdIn_ = nullptr;
return;
}
ssize_t bytesWritten = cgiStdIn_->write(httpRequest_->getBody().data(), httpRequest_->getBody().size());
if (bytesWritten < 0)
{
Log::error("Failed to write to CGI stdin, fd: " + std::to_string(cgiStdIn_->getFd()));
}
else
{
Log::debug("Wrote " + std::to_string(bytesWritten)
+ " bytes to CGI stdin, fd: " + std::to_string(cgiStdIn_->getFd()));
}
}
void Client::readFromCgi()
{
Log::trace(LOCATION);
if (cgiStdOut_ == nullptr)
{
Log::error("CGI stdout socket is null");
return;
}
char buffer[bufferSize_] = {}; // NOLINT(cppcoreguidelines-avoid-c-arrays)
ssize_t bytesRead
= cgiStdOut_->read(buffer, sizeof(buffer) - 1); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
if (bytesRead < 0)
{
Log::error("Failed to read from CGI stdout, fd: " + std::to_string(cgiStdOut_->getFd()));
}
else if (bytesRead == 0)
{
Log::info("CGI process closed stdout, fd: " + std::to_string(cgiStdOut_->getFd()));
server_.remove(*cgiStdOut_);
cgiStdOut_ = nullptr;
return;
}
else
{
buffer[bytesRead] = '\0'; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
httpResponse_->addHeader("Content-Type", "text/html");
httpResponse_->setBody(std::string(buffer, static_cast<size_t>(bytesRead)));
Log::debug("Read " + std::to_string(bytesRead)
+ " bytes from CGI stdout, fd: " + std::to_string(cgiStdOut_->getFd()));
}
}
void Client::setCgiSockets(std::unique_ptr<CgiSocket> cgiStdIn, std::unique_ptr<CgiSocket> cgiStdOut)
{
cgiStdIn->setCallback([this]() { writeToCgi(); });
cgiStdOut->setCallback([this]() { readFromCgi(); });
cgiStdOut_ = std::move(cgiStdOut);
cgiStdIn_ = std::move(cgiStdIn);
server_.add(*cgiStdOut_, EPOLLIN, this); // read
server_.add(*cgiStdIn_, EPOLLOUT, this); // write
// TODO add to handler
}
@ -116,24 +186,25 @@ void Client::poll() const
{
if (httpResponse_->isComplete())
{
Log::info("Response is ready to be sent to client, fd: " + std::to_string(client_socket_->getFd()));
client_socket_->setCallback([this]() { respond(); });
server_.responseReady(client_socket_->getFd());
Log::info("Response is ready to be sent to client, fd: " + std::to_string(clientSocket_->getFd()));
clientSocket_->setCallback([this]() { respond(); });
server_.responseReady(clientSocket_->getFd());
}
}
void Client::respond() const
{
auto payload = httpResponse_->toBytes();
ssize_t bytesSent = send(client_socket_->getFd(), payload.data(), payload.size(), 0);
ssize_t bytesSent = send(clientSocket_->getFd(), payload.data(), payload.size(), 0);
if (bytesSent < 0)
{
Log::error("Send failed for fd: " + std::to_string(client_socket_->getFd()));
Log::error("Send failed for fd: " + std::to_string(clientSocket_->getFd()));
}
else
{
Log::debug("Sent " + std::to_string(bytesSent) + " bytes to fd: " + std::to_string(client_socket_->getFd()));
Log::debug("Sent " + std::to_string(bytesSent) + " bytes to fd: " + std::to_string(clientSocket_->getFd()));
}
server_.disconnect(*this); // ! CRITICAL: RETURN IMMEDIATELY
}
HttpRequest &Client::getHttpRequest() const

View File

@ -13,9 +13,7 @@
#include <webserv/socket/ClientSocket.hpp> // for ClientSocket
#include <cstddef> // for size_t
#include <cstdint> // for uint8_t
#include <memory> // for unique_ptr
#include <vector> // for vector
class Server;
class ClientSocket;
@ -36,6 +34,10 @@ class Client
void request();
void respond() const;
void writeToCgi();
void readFromCgi();
void poll() const;
[[nodiscard]] int getStatusCode() const;
@ -43,18 +45,19 @@ class Client
[[nodiscard]] ASocket &getSocket(int fd = -1) const;
void setStatusCode(int code);
void setCgiSocket(std::unique_ptr<CgiSocket> cgiSocket);
void setCgiSockets(std::unique_ptr<CgiSocket> cgiStdIn, std::unique_ptr<CgiSocket> cgiStdOut);
[[nodiscard]] HttpRequest &getHttpRequest() const;
[[nodiscard]] HttpResponse &getHttpResponse() const;
private:
int statusCode_ = Http::StatusCode::OK;
constexpr static size_t bufferSize_ = 4096;
constexpr static size_t bufferSize_ = 8192 * 300; // 8KB buffer for reading CGI output
std::unique_ptr<HttpRequest> httpRequest_;
std::unique_ptr<HttpResponse> httpResponse_;
std::unique_ptr<Router> router_;
std::unique_ptr<ClientSocket> client_socket_;
std::unique_ptr<CgiSocket> cgi_socket_ = nullptr;
std::unique_ptr<ClientSocket> clientSocket_;
std::unique_ptr<CgiSocket> cgiStdIn_ = nullptr;
std::unique_ptr<CgiSocket> cgiStdOut_ = nullptr;
Server &server_;
};

View File

@ -5,13 +5,14 @@
#include <webserv/handler/URI.hpp>
#include <cstdlib>
#include <memory>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
CgiProcess::CgiProcess(const HttpRequest &request) : request_(request), _pid(-1), _cgiFd(-1)
CgiProcess::CgiProcess(const HttpRequest &request) : request_(request), _pid(-1)
{
if (!request_.getUri().isCgi())
{
@ -27,41 +28,56 @@ void CgiProcess::spawn()
auto cgiPath = uri.getCgiPath();
auto environment = uri.getCGIEnvironment();
int sv[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)
// pipes
int pipeStdin[2];
int pipeStdout[2];
if (pipe(pipeStdin) == -1 || pipe(pipeStdout) == -1)
{
throw std::runtime_error("Failed to create socket pair");
throw std::runtime_error("Failed to create pipes");
}
_cgiFd = sv[1];
_pid = fork();
if (_pid < 0)
{
close(sv[0]);
close(sv[1]);
close(pipeStdin[0]);
close(pipeStdin[1]);
close(pipeStdout[0]);
close(pipeStdout[1]);
throw std::runtime_error("Failed to fork");
}
if (_pid == 0)
{
dup2(sv[1], STDIN_FILENO);
dup2(sv[1], STDOUT_FILENO);
dup2(pipeStdin[0], STDIN_FILENO);
dup2(pipeStdout[1], STDOUT_FILENO);
close(sv[0]);
close(sv[1]);
close(pipeStdin[0]);
close(pipeStdin[1]);
close(pipeStdout[0]);
close(pipeStdout[1]);
// Log::debug("Executing CGI: " + cgiPath);
// Prepare arguments
std::string fullPath = uri.getFullPath();
char *args[] = {const_cast<char *>(cgiPath.c_str()), const_cast<char *>(fullPath.c_str())};
char *args[] = {const_cast<char *>(cgiPath.c_str()), const_cast<char *>(fullPath.c_str()), nullptr};
// Log::debug("With args:", {args[0], args[1]});
// TODO: Close all FDs
execve(const_cast<char *>(cgiPath.c_str()), args, nullptr);
exit(1);
}
else
{
// Parent process
auto cgiSocket = std::make_unique<CgiSocket>(sv[0]); // CgiSocket wraps sv[0]
close(sv[1]);
auto cgiStdIn = std::make_unique<CgiSocket>(pipeStdin[1]);
auto cgiStdOut = std::make_unique<CgiSocket>(pipeStdout[0]);
close(pipeStdin[0]);
close(pipeStdout[1]);
// cgiSocket->write(request_.getBody().data(), request_.getBody().size());
request_.getClient().setCgiSocket(std::move(cgiSocket)); // move the socket to the client
// _cgiFd can be used to communicate with the CGI process
Log::debug("CGI process forked with PID: " + std::to_string(_pid));
request_.getClient().setCgiSockets(std::move(cgiStdIn), std::move(cgiStdOut)); // move the sockets to the client
}
}

View File

@ -19,7 +19,7 @@ class CgiProcess
const HttpRequest &request_;
int _pid;
int _cgiFd;
// int _cgiFd;
void spawn();
};

View File

@ -76,6 +76,7 @@ void Server::add(const ASocket &socket, uint32_t events, Client *client)
Log::error("epoll_ctl ADD failed for fd: " + std::to_string(fd) + " with error: " + std::strerror(errno));
throw std::runtime_error("epoll_ctl ADD failed");
}
Log::debug("Socket added to epoll, fd: " + std::to_string(fd));
socketToClient_[fd] = client;
}
@ -144,7 +145,7 @@ ServerSocket &Server::getListener(int fd) const
return *listener;
}
}
Log::error("Listener not found for fd: " + std::to_string(fd));
Log::error("Listener not found for fd: " + std::to_string(fd) + ": " + std::strerror(errno));
throw std::runtime_error("Listener not found for fd: " + std::to_string(fd));
}
@ -165,8 +166,7 @@ void Server::handleRequest(struct epoll_event *event) const
Client &client = getClient(client_fd);
client.getSocket().callback();
client.getSocket(client_fd).callback();
}
void Server::responseReady(int client_fd) const
@ -185,10 +185,11 @@ void Server::responseReady(int client_fd) const
void Server::handleResponse(struct epoll_event *event)
{
Log::debug("Attempting to send data to fd: " + std::to_string(event->data.fd));
Client &client = getClient(event->data.fd);
client.getSocket().callback();
disconnect(client);
int socket_fd = event->data.fd;
Log::debug("Attempting to send data to fd: " + std::to_string(socket_fd));
Client &client = getClient(socket_fd);
client.getSocket(socket_fd).callback();
// disconnect(client);
}
void Server::handleEvent(struct epoll_event *event)
@ -196,7 +197,7 @@ void Server::handleEvent(struct epoll_event *event)
Log::trace(LOCATION);
if ((event->events & EPOLLERR) > 0 || (event->events & EPOLLHUP) > 0)
{
Log::error("Epoll error on fd " + std::to_string(event->data.fd));
Log::error("Epoll error on fd " + std::to_string(event->data.fd) + ": " + std::strerror(errno));
remove(getListener(event->data.fd));
close(event->data.fd);
}

View File

@ -1,9 +1,8 @@
#pragma once
#include <functional> // for function
#include <cstddef> // for size_t
#include <cstdint>
#include <functional> // for function
#include <sys/types.h> // for ssize_t
@ -33,8 +32,8 @@ class ASocket
void callback() const;
void setCallback(std::function<void()> callback);
ssize_t read(void *buf, size_t len) const;
ssize_t write(const 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;
protected:
void setNonBlocking() const;

View File

@ -1,6 +1,7 @@
#include <webserv/log/Log.hpp> // for LOCATION, Log
#include <webserv/socket/ASocket.hpp> // for ASocket
#include <webserv/socket/CgiSocket.hpp>
#include <unistd.h>
CgiSocket::CgiSocket(int fd) : ASocket(fd)
{
@ -11,3 +12,25 @@ ASocket::Type CgiSocket::getType() const
{
return ASocket::Type::CGI_SOCKET;
}
ssize_t CgiSocket::read(void *buf, size_t len) const
{
Log::trace(LOCATION);
ssize_t bytesRead = ::read(getFd(), buf, len);
if (bytesRead == -1)
{
throw std::system_error(errno, std::generic_category(), "Socket: Read error");
}
return bytesRead;
}
ssize_t CgiSocket::write(const void *buf, size_t len) const
{
Log::trace(LOCATION);
ssize_t bytesSent = ::write(getFd(), buf, len);
if (bytesSent == -1)
{
throw std::system_error(errno, std::generic_category(), "Socket: Write error");
}
return bytesSent;
}

View File

@ -12,4 +12,7 @@ class CgiSocket : public ASocket
explicit CgiSocket(int fd);
[[nodiscard]] ASocket::Type getType() const override;
ssize_t read(void *buf, size_t len) const override;
ssize_t write(const void *buf, size_t len) const override;
};