Implement Client and Server classes: add Client class for handling client requests and responses, and enhance Server class with connection management and event loop functionality.

This commit is contained in:
whaffman 2025-09-17 17:19:10 +02:00
parent a2db0659c6
commit 8e40f538cf
4 changed files with 160 additions and 77 deletions

22
webserv/server/Client.cpp Normal file
View File

@ -0,0 +1,22 @@
#include <iostream>
#include <webserv/server/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))
{
}
void Client::request(const std::string &req)
{
// Handle the request (placeholder implementation)
std::cout << "Client received request: " << req << '\n';
server.get().responseReady(client_fd);
}
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";
std::cout << response << '\n';
return response;
}

21
webserv/server/Client.hpp Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <functional>
#include <webserv/config/ServerConfig.hpp>
#include <webserv/server/Server.hpp>
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;
private:
int client_fd;
std::reference_wrapper<Server> server;
std::reference_wrapper<const ServerConfig> server_config;
};

View File

@ -1,47 +1,36 @@
#include <algorithm> // For std::find
#include <arpa/inet.h> // For inet_addr #include <arpa/inet.h> // For inet_addr
#include <cstdlib> // For exit() #include <cstdlib> // For exit()
#include <cstring> // For memset #include <cstring> // For memset
#include <fcntl.h> // For fcntl()" #include <fcntl.h> // For fcntl()"
#include <iostream> #include <iostream>
#include <map>
#include <netinet/in.h> // For sockaddr_in #include <netinet/in.h> // For sockaddr_in
#include <ranges> // For std::ranges::find
#include <sys/epoll.h> #include <sys/epoll.h>
#include <sys/socket.h> // For socket functions #include <sys/socket.h> // For socket functions
#include <unistd.h> // For close() #include <unistd.h> // For close()
#include <vector>
#include <webserv/server/Server.hpp> #include <webserv/server/Server.hpp>
Server::Server(const ConfigManager &configManager) : epoll_fd(-1) Server::Server(const ConfigManager &configManager) : _epoll_fd(-1), _configManager(configManager)
{ {
const auto &serverConfigs = configManager.getServerConfigs(); const auto &serverConfigs = configManager.getServerConfigs();
if (serverConfigs.empty()) if (serverConfigs.empty())
{ {
throw std::runtime_error("No server configurations available."); throw std::runtime_error("No server configurations available.");
} }
// for(auto serverConfig : serverConfigs) {
// setupServerSocket(serverConfig);
// }
// For simplicity, just take the first server config
const ServerConfig &config = serverConfigs[0];
host = config.getHost();
port = config.getPort();
std::cout << "Server initialized on " << host << ":" << port << '\n';
} }
int createServerSocket(const std::string &host, int port)
void Server::start()
{ {
int server_fd = -1; int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// 1. Create socket (IPv4, TCP)
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) if (server_fd == -1)
{ {
perror("Socket creation failed"); perror("Socket creation failed");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
// 2. Set socket options (to reuse address and port)
int opt = 1; int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
{ {
@ -50,13 +39,10 @@ void Server::start()
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
// 3. Bind socket to an IP/Port
struct sockaddr_in address{}; struct sockaddr_in address{};
// std::memset(&address, 0, sizeof(address));
address.sin_family = AF_INET; address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // Listen on all interfaces address.sin_addr.s_addr = inet_addr(host.c_str());
address.sin_port = htons(port); // Host-to-network byte order address.sin_port = htons(port);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) // NOLINT if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) // NOLINT
{ {
@ -65,7 +51,6 @@ void Server::start()
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
// 4. Listen for incoming connections
if (listen(server_fd, SOMAXCONN) < 0) if (listen(server_fd, SOMAXCONN) < 0)
{ {
perror("Listen failed"); perror("Listen failed");
@ -74,32 +59,64 @@ void Server::start()
} }
fcntl(server_fd, F_SETFL, O_NONBLOCK); fcntl(server_fd, F_SETFL, O_NONBLOCK);
std::cout << "Server listening on port " << port << "...\n"; 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 // 5. Set up epoll
int epoll_fd = epoll_create1(0); int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) if (epoll_fd == -1)
{ {
perror("epoll_create1 failed"); perror("epoll_create1 failed");
close(server_fd); // TODO: Close all server FDs
// throw exception // for (int server_fd : _server_fds)
// {
// close(server_fd);
// }
} }
struct epoll_event event; for (const auto &pair : _fdToConfig)
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"); int server_fd = pair.first;
close(server_fd); struct epoll_event event{};
close(epoll_fd); event.events = EPOLLIN; // Interested in read events
// throw exception 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; const int MAX_EVENTS = 10;
struct epoll_event events[MAX_EVENTS]; // NOLINT struct epoll_event events[MAX_EVENTS]; // NOLINT
while (true) 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) if (nfds == -1)
{ {
perror("epoll_wait failed"); perror("epoll_wait failed");
@ -107,29 +124,33 @@ void Server::start()
} }
for (int i = 0; i < nfds; ++i) for (int i = 0; i < nfds; ++i)
{ {
epoll_event &event = events[i]; epoll_event &event = events[i];
if ((event.events & EPOLLERR) > 0 || (event.events & EPOLLHUP) > 0) if ((event.events & EPOLLERR) > 0 || (event.events & EPOLLHUP) > 0)
{ {
std::cerr << "Epoll error on fd " << event.data.fd << '\n'; std::cerr << "Epoll error on fd " << event.data.fd << '\n';
close(event.data.fd); close(event.data.fd);
} }
if ((event.events & EPOLLIN) > 0 && event.data.fd == server_fd) else if (_fdToConfig.contains(event.data.fd))
{ {
handleConnection(epoll_fd, &event); handleConnection(_epoll_fd, &event);
} }
else if ((event.events & EPOLLIN) > 0) else if ((event.events & EPOLLIN) > 0)
{ {
handleRequest(epoll_fd, &event); handleRequest(_epoll_fd, &event);
} else if ((event.events & EPOLLOUT) > 0) { }
std::cout << "Attempting to send data to fd: " << event.data.fd << '\n'; else if ((event.events & EPOLLOUT) > 0)
const char *httpResponse = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, World!"; {
ssize_t bytesSent = send(event.data.fd, httpResponse, strlen(httpResponse), 0); std::cout << "Attempting to send data to fd: " << event.data.fd << '\n';
if (bytesSent < 0) std::string response = clients.at(event.data.fd).getResponse();
{ const char *httpResponse = response.c_str();
perror("Send error"); ssize_t bytesSent = send(event.data.fd, httpResponse, strlen(httpResponse), 0);
} if (bytesSent < 0)
close(event.data.fd); // Close after sending response {
} perror("Send error");
}
clients.erase(event.data.fd);
close(event.data.fd); // Close after sending response
}
} }
} }
} }
@ -138,35 +159,43 @@ void Server::handleRequest(int epoll_fd, struct epoll_event *event)
{ {
std::cout << "Handling request...\n"; std::cout << "Handling request...\n";
int client_fd = event->data.fd; int client_fd = event->data.fd;
std::cout << "client fd: " << client_fd << '\n'; std::cout << "client fd: " << client_fd << '\n';
char buffer[1024] = {}; char buffer[1024] = {};
ssize_t bytesRead = read(client_fd, buffer, sizeof(buffer) - 1); ssize_t bytesRead = read(client_fd, buffer, sizeof(buffer) - 1);
if (bytesRead < 0) if (bytesRead < 0)
{ {
perror("Read error"); perror("Read error");
close(client_fd); close(client_fd);
return; return;
} }
if (bytesRead == 0) if (bytesRead == 0)
{ {
std::cout << "Client disconnected, fd: " << client_fd << '\n'; std::cout << "Client disconnected, fd: " << client_fd << '\n';
close(client_fd); close(client_fd);
return; return;
} }
buffer[bytesRead] = '\0'; // Null-terminate the buffer buffer[bytesRead] = '\0'; // Null-terminate the buffer
std::cout << "Received request:\n" << buffer << '\n'; 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; struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET; ev.events = EPOLLOUT | EPOLLET;
ev.data.fd = client_fd; ev.data.fd = client_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_MOD, client_fd, &ev); epoll_ctl(_epoll_fd, EPOLL_CTL_MOD, client_fd, &ev);
} }
void Server::handleConnection(int epoll_fd, struct epoll_event *event) void Server::handleConnection(int epoll_fd, struct epoll_event *event)
{ {
int client_fd = accept(event->data.fd, nullptr, nullptr); int client_fd = accept(event->data.fd, nullptr, nullptr);
std::cout << "connection " << client_fd << '\n';
fcntl(client_fd, F_SETFL, O_NONBLOCK); fcntl(client_fd, F_SETFL, O_NONBLOCK);
if (client_fd == -1) if (client_fd == -1)
{ {
@ -184,5 +213,7 @@ void Server::handleConnection(int epoll_fd, struct epoll_event *event)
close(epoll_fd); close(epoll_fd);
// throw exception // throw exception
} }
// close(client_fd); // Close the client socket for now
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,21 +1,30 @@
#pragma once #pragma once
#include "webserv/config/ConfigManager.hpp" #include <functional>
#include <webserv/config/ConfigManager.hpp>
#include <webserv/server/Client.hpp>
class Client;
class Server class Server
{ {
public: public:
Server(const ConfigManager &configManager); Server(const ConfigManager &configManager);
// The constructor must initialize the reference member 'configManager'
// Implementation should be in the .cpp file using an initializer list
void start(); void start();
void setupServerSocket(ServerConfig &config); void setupServerSocket(ServerConfig &config);
void handleConnection(int epoll_fd, struct epoll_event *event); void handleConnection(int epoll_fd, struct epoll_event *event);
void handleRequest(int epoll_fd, struct epoll_event *event); void handleRequest(int epoll_fd, struct epoll_event *event);
void responseReady(int client_fd);
void eventLoop();
private: private:
int _epoll_fd;
int epoll_fd; std::reference_wrapper<const ConfigManager> _configManager;
std::string host; std::map<int, std::reference_wrapper<const ServerConfig>> _fdToConfig;
int port; std::vector<int> _server_fds;
std::map<int, Client> clients;
}; };