Refactor Client and Server classes to use unique_ptr for socket management, enhance request handling, and improve connection lifecycle management

This commit is contained in:
Quinten Mennen 2025-09-18 15:07:19 +02:00
parent fc5e59689f
commit db8b078c27
6 changed files with 424 additions and 168 deletions

View File

@ -1,22 +1,44 @@
#include "webserv/socket/Socket.hpp"
#include <iostream>
#include <webserv/client/Client.hpp>
Client::Client(int client_fd, Server &server, const ServerConfig &server_config)
: client_fd(client_fd), server(std::ref(server)), server_config(std::cref(server_config))
Client::Client(std::unique_ptr<Socket> socket, Server &server, const ServerConfig &server_config)
: client_socket_(std::move(socket)), server(std::ref(server)), server_config(std::cref(server_config))
{
}
void Client::request(const std::string &req)
Client::~Client(){
std::cout << "Client destructor called for fd: " << client_socket_->getFd() << '\n';
server.removeFromEpoll(*client_socket_);
};
void Client::request()
{
char buffer[1024] = {};
ssize_t bytesRead = client_socket_->recv(buffer, sizeof(buffer) - 1);
if (bytesRead < 0)
{
perror("Read error");
return;
}
if (bytesRead == 0)
{
std::cout << "Client disconnected, fd: " << client_socket_->getFd() << '\n';
return;
}
buffer[bytesRead] = '\0'; // Null-terminate the buffer
std::cout << "Received request:\n" << buffer << '\n';
// Handle the request (placeholder implementation)
std::cout << "Client received request: " << req << '\n';
server.get().responseReady(client_fd);
server.responseReady(client_socket_->getFd());
}
std::string Client::getResponse() const
{
std::string response = "HTTP/1.1 200 OK\r\nContent-Length: 32\r\n\r\nHello, World!";
response += " Server port " + std::to_string(server_config.get().getPort()) + "\r\n";
response += " Server port " + std::to_string(server_config.getPort()) + "\r\n";
std::cout << response << '\n';
return response;
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <functional>
#include "webserv/socket/Socket.hpp"
#include <memory>
#include <webserv/config/ServerConfig.hpp>
#include <webserv/server/Server.hpp>
@ -9,13 +10,21 @@ class Server;
class Client
{
public:
Client(int client_fd, Server &server, const ServerConfig &server_config);
void request(const std::string &req);
std::string getResponse() const;
Client(std::unique_ptr<Socket> socket, Server &server, const ServerConfig &server_config);
Client(const Client &other) = delete; // Disable copy constructor
Client &operator=(const Client &other) = delete; // Disable copy assignment
Client(Client &&other) noexcept = delete; // Move constructor
Client &operator=(Client &&other) noexcept = delete; // Move assignment
~Client();
void request();
[[nodiscard]] std::string getResponse() const;
private:
int client_fd;
std::reference_wrapper<Server> server;
std::reference_wrapper<const ServerConfig> server_config;
std::unique_ptr<Socket> client_socket_;
const Server &server;
const ServerConfig &server_config;
};

View File

@ -1,112 +1,167 @@
#include "webserv/socket/Socket.hpp"
#include <algorithm> // For std::find
#include <arpa/inet.h> // For inet_addr
#include <cstdlib> // For exit()
#include <cstring> // For memset
#include <fcntl.h> // For fcntl()"
#include <iostream>
#include <memory>
#include <netinet/in.h> // For sockaddr_in
#include <ranges> // For std::ranges::find
#include <sys/epoll.h>
#include <sys/socket.h> // For socket functions
#include <unistd.h> // For close()
#include <vector>
#include <webserv/server/Server.hpp>
Server::Server(const ConfigManager &configManager) : _epoll_fd(-1), _configManager(configManager)
Server::Server(const ConfigManager &configManager) : epoll_fd_(epoll_create1(0)), configManager_(configManager)
{
const auto &serverConfigs = configManager.getServerConfigs();
if (serverConfigs.empty())
{
throw std::runtime_error("No server configurations available.");
}
if (epoll_fd_ == -1)
{
throw std::runtime_error("epoll_create1 failed");
}
}
int createServerSocket(const std::string &host, int port)
Server::~Server()
{
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1)
if (epoll_fd_ != -1)
{
perror("Socket creation failed");
exit(EXIT_FAILURE);
close(epoll_fd_);
}
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
{
perror("setsockopt");
close(server_fd);
exit(EXIT_FAILURE);
}
struct sockaddr_in address{};
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr(host.c_str());
address.sin_port = htons(port);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) // NOLINT
{
perror("Bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
if (listen(server_fd, SOMAXCONN) < 0)
{
perror("Listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
fcntl(server_fd, F_SETFL, O_NONBLOCK);
std::cout << "Server listening on " << host << ":" << port << "...\n";
return server_fd;
}
void Server::start()
{
std::cout << "Starting servers...\n";
// 1. Load server configurations
for (const auto &config : _configManager.get().getServerConfigs())
for (const auto &config : configManager_.getServerConfigs())
{
int server_fd = createServerSocket(config.getHost(), config.getPort());
_fdToConfig.insert({server_fd, std::cref(config)});
setupServerSocket(config);
}
if (_fdToConfig.empty())
if (fdToConfig_.empty())
{
throw std::runtime_error("No server sockets created.");
}
// 5. Set up epoll
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1)
{
perror("epoll_create1 failed");
// TODO: Close all server FDs
// for (int server_fd : _server_fds)
// {
// close(server_fd);
// }
}
for (const auto &pair : _fdToConfig)
{
int server_fd = pair.first;
struct epoll_event event{};
event.events = EPOLLIN; // Interested in read events
event.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1)
{
perror("epoll_ctl failed");
// TODO close fds
close(epoll_fd);
// throw exception
}
// _server_fds.push_back(server_fd);
}
_epoll_fd = epoll_fd;
eventLoop();
}
void Server::addToEpoll(const Socket &socket, uint32_t events) const
{
int fd = socket.getFd();
struct epoll_event event{};
event.events = events;
event.data.fd = fd;
if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &event) == -1)
{
throw std::runtime_error("epoll_ctl ADD failed");
}
}
void Server::removeFromEpoll(const Socket &socket) const
{
int filedes = socket.getFd();
if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, filedes, nullptr) == -1)
{
throw std::runtime_error("epoll_ctl DEL failed");
}
}
void Server::setupServerSocket(const ServerConfig &config)
{
try
{
std::unique_ptr<Socket> serverSocket = std::make_unique<Socket>();
serverSocket->bind(config.getHost(), config.getPort());
serverSocket->listen(SOMAXCONN);
int server_fd = serverSocket->getFd();
addToEpoll(*serverSocket, EPOLLIN);
listeners_.push_back(std::move(serverSocket));
fdToConfig_.insert({server_fd, std::cref(config)});
std::cout << "Server listening on " << config.getHost() << ":" << config.getPort() << "...\n";
}
catch (const std::exception &e)
{
std::cerr << "Error setting up server socket: " << e.what() << '\n';
}
}
void Server::handleConnection(struct epoll_event *event)
{
Socket &listener = getListener(event->data.fd);
std::unique_ptr<Socket> clientSocket = listener.accept();
addToEpoll(*clientSocket, EPOLLIN | EPOLLET);
clients_.insert(
{clientSocket->getFd(), std::make_unique<Client>(std::move(clientSocket), *this, getConfig(listener))});
// clients_.insert({clientSocket->getFd(), Client{std::move(clientSocket), *this, getConfig(listener)}});
}
Socket &Server::getListener(int fd) const
{
for (const auto &listener : listeners_)
{
if (listener->getFd() == fd)
{
return *listener;
}
}
throw std::runtime_error("Listener not found for fd: " + std::to_string(fd));
}
Client &Server::getClient(int fd) const
{
auto it = clients_.find(fd);
if (it != clients_.end())
{
return *(it->second);
}
throw std::runtime_error("Client not found for fd: " + std::to_string(fd));
}
const ServerConfig &Server::getConfig(const Socket &socket) const
{
return getConfig(socket.getFd());
}
const ServerConfig &Server::getConfig(int fd) const
{
auto it = fdToConfig_.find(fd);
if (it != fdToConfig_.end())
{
return (it->second.get());
}
throw std::runtime_error("Config not found for fd: " + std::to_string(fd));
}
void Server::handleRequest(struct epoll_event *event)
{
std::cout << "Handling request...\n";
int client_fd = event->data.fd;
Client &client = getClient(client_fd);
client.request();
}
void Server::responseReady(int client_fd) const
{
std::cout << "Response ready for client fd: " << client_fd << '\n';
struct epoll_event ev{};
ev.events = EPOLLOUT | EPOLLET;
ev.data.fd = client_fd;
if (epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, client_fd, &ev) == -1)
{
throw std::runtime_error("epoll_ctl MOD failed");
}
}
void Server::eventLoop()
{
// Placeholder for the event loop logic
@ -115,11 +170,10 @@ void Server::eventLoop()
struct epoll_event events[MAX_EVENTS]; // NOLINT
while (true)
{
int nfds = epoll_wait(_epoll_fd, events, MAX_EVENTS, -1); // NOLINT
int nfds = epoll_wait(epoll_fd_, events, MAX_EVENTS, -1); // NOLINT
if (nfds == -1)
{
perror("epoll_wait failed");
break;
throw std::runtime_error("epoll_wait failed");
}
for (int i = 0; i < nfds; ++i)
{
@ -127,92 +181,235 @@ void Server::eventLoop()
if ((event.events & EPOLLERR) > 0 || (event.events & EPOLLHUP) > 0)
{
std::cerr << "Epoll error on fd " << event.data.fd << '\n';
removeFromEpoll(getListener(event.data.fd));
close(event.data.fd);
}
else if (_fdToConfig.contains(event.data.fd))
else if (fdToConfig_.contains(event.data.fd))
{
handleConnection(_epoll_fd, &event);
handleConnection(&event);
}
else if ((event.events & EPOLLIN) > 0)
{
handleRequest(_epoll_fd, &event);
handleRequest(&event);
}
else if ((event.events & EPOLLOUT) > 0)
{
std::cout << "Attempting to send data to fd: " << event.data.fd << '\n';
std::string response = clients.at(event.data.fd).getResponse();
Client &client = getClient(event.data.fd);
std::string response = client.getResponse();
const char *httpResponse = response.c_str();
ssize_t bytesSent = send(event.data.fd, httpResponse, strlen(httpResponse), 0);
if (bytesSent < 0)
{
perror("Send error");
}
clients.erase(event.data.fd);
close(event.data.fd); // Close after sending response
clients_.erase(event.data.fd);
}
}
}
}
void Server::handleRequest(int epoll_fd, struct epoll_event *event)
{
std::cout << "Handling request...\n";
// Server::Server(const ConfigManager &configManager) : _epoll_fd(-1), _configManager(configManager)
// {
// const auto &serverConfigs = configManager.getServerConfigs();
// if (serverConfigs.empty())
// {
// throw std::runtime_error("No server configurations available.");
// }
// }
int client_fd = event->data.fd;
std::cout << "client fd: " << client_fd << '\n';
char buffer[1024] = {};
ssize_t bytesRead = read(client_fd, buffer, sizeof(buffer) - 1);
if (bytesRead < 0)
{
perror("Read error");
close(client_fd);
return;
}
if (bytesRead == 0)
{
std::cout << "Client disconnected, fd: " << client_fd << '\n';
close(client_fd);
return;
}
// int createServerSocket(const std::string &host, int port)
// {
// int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// if (server_fd == -1)
// {
// perror("Socket creation failed");
// exit(EXIT_FAILURE);
// }
buffer[bytesRead] = '\0'; // Null-terminate the buffer
std::cout << "Received request:\n" << buffer << '\n';
// int opt = 1;
// if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
// {
// perror("setsockopt");
// close(server_fd);
// exit(EXIT_FAILURE);
// }
// clients[client_fd] = Client(client_fd, *this, ServerConfig);
clients.at(client_fd).request(buffer);
}
// struct sockaddr_in address{};
// address.sin_family = AF_INET;
// address.sin_addr.s_addr = inet_addr(host.c_str());
// address.sin_port = htons(port);
void Server::responseReady(int client_fd)
{
std::cout << "Response ready for client fd: " << client_fd << '\n';
struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET;
ev.data.fd = client_fd;
epoll_ctl(_epoll_fd, EPOLL_CTL_MOD, client_fd, &ev);
}
// if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) // NOLINT
// {
// perror("Bind failed");
// close(server_fd);
// exit(EXIT_FAILURE);
// }
void Server::handleConnection(int epoll_fd, struct epoll_event *event)
{
int client_fd = accept(event->data.fd, nullptr, nullptr);
std::cout << "connection " << client_fd << '\n';
fcntl(client_fd, F_SETFL, O_NONBLOCK);
if (client_fd == -1)
{
perror("Accept failed");
return;
}
std::cout << "New connection accepted, fd: " << client_fd << '\n';
struct epoll_event client_event;
client_event.events = EPOLLIN | EPOLLET;
client_event.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_event) == -1)
{
perror("epoll_ctl failed");
close(client_fd);
close(epoll_fd);
// throw exception
}
// if (listen(server_fd, SOMAXCONN) < 0)
// {
// perror("Listen failed");
// close(server_fd);
// exit(EXIT_FAILURE);
// }
std::cout << "client fd " << client_fd << ", server fd " << event->data.fd << '\n';
clients.insert({client_fd, Client(client_fd, *this, _fdToConfig.at(event->data.fd))});
}
// fcntl(server_fd, F_SETFL, O_NONBLOCK);
// std::cout << "Server listening on " << host << ":" << port << "...\n";
// return server_fd;
// }
// void Server::start()
// {
// std::cout << "Starting servers...\n";
// // 1. Load server configurations
// for (const auto &config : _configManager.get().getServerConfigs())
// {
// int server_fd = createServerSocket(config.getHost(), config.getPort());
// _fdToConfig.insert({server_fd, std::cref(config)});
// }
// if (_fdToConfig.empty())
// {
// throw std::runtime_error("No server sockets created.");
// }
// // 5. Set up epoll
// int epoll_fd = epoll_create1(0);
// if (epoll_fd == -1)
// {
// perror("epoll_create1 failed");
// // TODO: Close all server FDs
// // for (int server_fd : _server_fds)
// // {
// // close(server_fd);
// // }
// }
// for (const auto &pair : _fdToConfig)
// {
// int server_fd = pair.first;
// struct epoll_event event{};
// event.events = EPOLLIN; // Interested in read events
// event.data.fd = server_fd;
// if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1)
// {
// perror("epoll_ctl failed");
// // TODO close fds
// close(epoll_fd);
// // throw exception
// }
// // _server_fds.push_back(server_fd);
// }
// _epoll_fd = epoll_fd;
// eventLoop();
// }
// void Server::eventLoop()
// {
// // Placeholder for the event loop logic
// std::cout << "Entering event loop...\n";
// const int MAX_EVENTS = 10;
// struct epoll_event events[MAX_EVENTS]; // NOLINT
// while (true)
// {
// int nfds = epoll_wait(_epoll_fd, events, MAX_EVENTS, -1); // NOLINT
// if (nfds == -1)
// {
// perror("epoll_wait failed");
// break;
// }
// for (int i = 0; i < nfds; ++i)
// {
// epoll_event &event = events[i];
// if ((event.events & EPOLLERR) > 0 || (event.events & EPOLLHUP) > 0)
// {
// std::cerr << "Epoll error on fd " << event.data.fd << '\n';
// close(event.data.fd);
// }
// else if (_fdToConfig.contains(event.data.fd))
// {
// handleConnection(_epoll_fd, &event);
// }
// else if ((event.events & EPOLLIN) > 0)
// {
// handleRequest(_epoll_fd, &event);
// }
// else if ((event.events & EPOLLOUT) > 0)
// {
// std::cout << "Attempting to send data to fd: " << event.data.fd << '\n';
// std::string response = clients.at(event.data.fd).getResponse();
// const char *httpResponse = response.c_str();
// ssize_t bytesSent = send(event.data.fd, httpResponse, strlen(httpResponse), 0);
// if (bytesSent < 0)
// {
// perror("Send error");
// }
// clients.erase(event.data.fd);
// close(event.data.fd); // Close after sending response
// }
// }
// }
// }
// void Server::handleRequest(int epoll_fd, struct epoll_event *event)
// {
// std::cout << "Handling request...\n";
// int client_fd = event->data.fd;
// std::cout << "client fd: " << client_fd << '\n';
// char buffer[1024] = {};
// ssize_t bytesRead = read(client_fd, buffer, sizeof(buffer) - 1);
// if (bytesRead < 0)
// {
// perror("Read error");
// close(client_fd);
// return;
// }
// if (bytesRead == 0)
// {
// std::cout << "Client disconnected, fd: " << client_fd << '\n';
// close(client_fd);
// return;
// }
// buffer[bytesRead] = '\0'; // Null-terminate the buffer
// std::cout << "Received request:\n" << buffer << '\n';
// // clients[client_fd] = Client(client_fd, *this, ServerConfig);
// clients.at(client_fd).request(buffer);
// }
// void Server::responseReady(int client_fd)
// {
// std::cout << "Response ready for client fd: " << client_fd << '\n';
// struct epoll_event ev;
// ev.events = EPOLLOUT | EPOLLET;
// ev.data.fd = client_fd;
// epoll_ctl(_epoll_fd, EPOLL_CTL_MOD, client_fd, &ev);
// }
// void Server::handleConnection(int epoll_fd, struct epoll_event *event)
// {
// int client_fd = accept(event->data.fd, nullptr, nullptr);
// std::cout << "connection " << client_fd << '\n';
// fcntl(client_fd, F_SETFL, O_NONBLOCK);
// if (client_fd == -1)
// {
// perror("Accept failed");
// return;
// }
// std::cout << "New connection accepted, fd: " << client_fd << '\n';
// struct epoll_event client_event;
// client_event.events = EPOLLIN | EPOLLET;
// client_event.data.fd = client_fd;
// if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_event) == -1)
// {
// perror("epoll_ctl failed");
// close(client_fd);
// close(epoll_fd);
// // throw exception
// }
// std::cout << "client fd " << client_fd << ", server fd " << event->data.fd << '\n';
// clients.insert({client_fd, Client(client_fd, *this, _fdToConfig.at(event->data.fd))});
// }

View File

@ -1,30 +1,49 @@
#pragma once
#include "webserv/config/ServerConfig.hpp"
#include <functional>
#include <webserv/config/ConfigManager.hpp>
#include <memory>
#include <unordered_map>
#include <webserv/client/Client.hpp>
#include <webserv/config/ConfigManager.hpp>
#include <webserv/socket/Socket.hpp>
class Client;
class Server
{
public:
Server() = delete;
Server(const ConfigManager &configManager);
Server(const Server &other) = delete; // Disable copy constructor
Server &operator=(const Server &other) = delete; // Disable copy assignment
Server(Server &&other) noexcept = delete; // Move constructor
Server &operator=(Server &&other) noexcept = delete; // Move assignment
// The constructor must initialize the reference member 'configManager'
// Implementation should be in the .cpp file using an initializer list
~Server();
void start();
void setupServerSocket(ServerConfig &config);
void handleConnection(int epoll_fd, struct epoll_event *event);
void handleRequest(int epoll_fd, struct epoll_event *event);
void responseReady(int client_fd);
void addToEpoll(const Socket &socket, uint32_t events) const;
void removeFromEpoll(const Socket &socket) const ;
void setupServerSocket(const ServerConfig &config);
void handleConnection(struct epoll_event *event);
void handleRequest(struct epoll_event *event);
void responseReady(int client_fd) const;
void eventLoop();
Socket &getListener(int fd) const;
Client &getClient(int fd) const;
const ServerConfig &getConfig(int fd) const;
const ServerConfig &getConfig(const Socket &socket) const;
private:
int _epoll_fd;
std::reference_wrapper<const ConfigManager> _configManager;
std::map<int, std::reference_wrapper<const ServerConfig>> _fdToConfig;
std::vector<int> _server_fds;
std::map<int, Client> clients;
int epoll_fd_;
const ConfigManager &configManager_;
std::vector<std::unique_ptr<Socket>> listeners_;
std::unordered_map<int, std::reference_wrapper<const ServerConfig>> fdToConfig_;
std::unordered_map<int, std::unique_ptr<Client>> clients_;
};

View File

@ -1,3 +1,4 @@
#include <memory>
#include <webserv/socket/Socket.hpp>
#include <stdexcept>
@ -21,6 +22,7 @@ Socket::Socket() : _fd(socket(AF_INET, SOCK_STREAM, 0))
_fd = -1;
throw std::runtime_error("setsockopt failed");
}
setNonBlocking();
}
Socket::Socket(int fd) : _fd(fd) // NOLINT readability-identifier-naming
@ -29,6 +31,7 @@ Socket::Socket(int fd) : _fd(fd) // NOLINT readability-identifier-naming
{
throw std::runtime_error("Invalid file descriptor");
}
setNonBlocking();
}
Socket::~Socket()
@ -54,20 +57,20 @@ void Socket::bind(const std::string &host, const int port) const
address.sin_addr.s_addr = inet_addr(host.c_str());
address.sin_port = htons(port);
if (::bind(_fd, (struct sockaddr *)&address, sizeof(address)) < 0) // NOLINT cppcoreguidelines-pro-type-cstyle-cast
if (::bind(_fd, (struct sockaddr *)&address, sizeof(address)) < 0) // NOLINT(cppcoreguidelines-pro-type-cstyle-cast
{
throw std::runtime_error("Bind failed");
}
}
Socket Socket::accept() const
std::unique_ptr<Socket> Socket::accept() const
{
int client_fd = ::accept(_fd, nullptr, nullptr);
if (client_fd < 0)
{
throw std::runtime_error("Accept failed");
}
return {client_fd};
return std::make_unique<Socket>(client_fd);
}
ssize_t Socket::recv(void *buf, size_t len) const
@ -87,3 +90,8 @@ void Socket::setNonBlocking() const
throw std::runtime_error("Failed to set non-blocking mode");
}
}
int Socket::getFd() const
{
return _fd;
}

View File

@ -1,7 +1,8 @@
#pragma once
#include <sys/types.h>
#include <memory>
#include <string>
#include <sys/types.h>
class Socket
{
@ -9,20 +10,20 @@ class Socket
Socket();
Socket(int fd); // NOLINT readability-identifier-naming
Socket(const Socket &other) = delete; // Disable copy constructor
Socket &operator=(const Socket &other) = delete; // Disable copy assignment
Socket(Socket &&other) noexcept; // Move constructor
Socket &operator=(Socket &&other) noexcept; // Move assignment
Socket(const Socket &other) = delete; // Disable copy constructor
Socket &operator=(const Socket &other) = delete; // Disable copy assignment
Socket(Socket &&other) noexcept = default; // Move constructor
Socket &operator=(Socket &&other) noexcept = default; // Move assignment
~Socket();
void listen(int backlog) const;
void bind(const std::string &host, const int port) const;
[[nodiscard]] Socket accept() const;
[[nodiscard]] std::unique_ptr<Socket> accept() const;
ssize_t recv(void *buf, size_t len) const;
ssize_t send(const void *buf, size_t len) const;
void setNonBlocking() const;
[[nodiscard]] int getFd() const;
[[nodiscard]] int getFd() const;
private:
int _fd;