feat: CGI
This commit is contained in:
parent
e1be8d8aa8
commit
d562c5bcbf
@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
echo "Hello, World! This is site-3.";
|
||||
phpinfo();
|
||||
@ -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
|
||||
|
||||
@ -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_;
|
||||
};
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -19,7 +19,7 @@ class CgiProcess
|
||||
const HttpRequest &request_;
|
||||
|
||||
int _pid;
|
||||
int _cgiFd;
|
||||
// int _cgiFd;
|
||||
|
||||
void spawn();
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user