LeftHookRoll
An HTTP/1.0 compliant web server, as specified by RFC1945
Loading...
Searching...
No Matches
ServerManager.cpp
Go to the documentation of this file.
1#include "../includes/ServerManager.hpp"
2#include "../includes/FatalExceptions.hpp"
3#include "../includes/CGIManager.hpp"
4
5#include <iostream>
6#include <cstring>
7#include <cerrno>
8#include <csignal>
9#include <arpa/inet.h>
10#include <unistd.h>
11#include <fcntl.h>
12
13// Defined in main.cpp — temp implementation.
14extern volatile sig_atomic_t g_running;
15
16// --- Canonical Form ---
18{
19 _epollFd = epoll_create(1);
20 if (_epollFd < 0)
21 throw FatalException(std::string("epoll_create(): ") + strerror(errno));
22 _eventBuffer.resize(64);
23}
24
25ServerManager::ServerManager(std::vector<ServerConf> confsCopy) : _epollFd(-1)
26{
27 _epollFd = epoll_create(1);
28 if (_epollFd < 0)
29 throw FatalException(std::string("epoll_create(): ") + strerror(errno));
30 _eventBuffer.resize(64);
31
32 try
33 {
34 for (size_t i = 0; i < confsCopy.size(); ++i)
35 {
36 _serverConfs.push_back(new ServerConf(confsCopy[i]));
37 addServer(_serverConfs.back());
38 }
39 }
40 catch (...)
41 {
42 for (size_t i = 0; i < _serverConfs.size(); ++i)
43 delete _serverConfs[i];
44 _serverConfs.clear();
46 throw;
47 }
48}
49
51 : _interfacePortPairs(other._interfacePortPairs),
52 _epollFd(-1),
53 _eventBuffer(other._eventBuffer),
54 _fdEvents(),
55 _listenFds(other._listenFds),
56 _listenFdToServerConf(other._listenFdToServerConf)
57{
58 _epollFd = epoll_create(1);
59 if (_epollFd < 0)
60 throw FatalException(std::string("epoll_create(): ") + strerror(errno));
61 try
62 {
63 for (std::map<int, uint32_t>::const_iterator it = other._fdEvents.begin();
64 it != other._fdEvents.end(); ++it)
65 {
66 addPollFd(it->first, it->second);
67 }
68 }
69 catch (...)
70 {
72 throw;
73 }
74}
75
77{
78 if (this != &other)
79 {
82 _listenFds = other._listenFds;
85 _epollFd = epoll_create(1);
86 if (_epollFd < 0)
87 throw FatalException(std::string("epoll_create(): ") + strerror(errno));
88 for (std::map<int, uint32_t>::const_iterator it = other._fdEvents.begin();
89 it != other._fdEvents.end(); ++it)
90 {
91 addPollFd(it->first, it->second);
92 }
93 }
94 return *this;
95}
96
98{
99 for(size_t i = 0; i < _serverConfs.size(); i++)
100 delete _serverConfs[i];
101 _closeAllFds();
103}
104
105// --- Public Interface ---
106
108{
109 struct sockaddr_in addr = conf->getInterfacePortPair();
110 int fd = _createListeningSocket(addr);
111
112 _interfacePortPairs[addr] = conf;
113 _listenFds.insert(fd);
114 _listenFdToServerConf[fd] = conf;
115 addPollFd(fd, EPOLLIN);
116
117 std::cout << "Server "<< conf->getServerName() << " Listening on "
118 << req_utils::ipv4ToString(addr) << ":"
119 << ntohs(addr.sin_port) << std::endl;
120}
121
123{
124 /**
125 * @brief listen without needing a conf file.
126 *
127 */
128 struct sockaddr_in addr;
129 std::memset(&addr, 0, sizeof(addr));
130 addr.sin_family = AF_INET;
131 addr.sin_addr.s_addr = htonl(INADDR_ANY);
132 addr.sin_port = htons(port);
133
134 int fd = _createListeningSocket(addr);
135 _listenFds.insert(fd);
136 _listenFdToServerConf[fd] = NULL;
137 addPollFd(fd, EPOLLIN);
138
139
140 std::cout << "Listening on 0.0.0.0:" << port << std::endl;
141}
142
144{
145 while (g_running)
146 {
150
151 // Use a finite timeout so periodic tasks like _sweepTimeouts() still run when idle.
152 int ePollTimeOut = _processingQueue.empty() ? 1000 : 0; // ms; 0 = non-blocking when there is work.
153 int ready = epoll_wait(_epollFd, &_eventBuffer[0],
154 static_cast<int>(_eventBuffer.size()), ePollTimeOut);
155 if (ready <= 0)
156 {
157 if (ready == 0 || errno == EINTR)
158 continue;
159 throw FatalException(std::string("epoll_wait(): ") + strerror(errno));
160 }
161
162 for (int i = 0; i < ready; ++i)
163 {
164 int fd = _eventBuffer[i].data.fd;
165 uint32_t events = _eventBuffer[i].events;
166
167 if (_cgiPipeToConn.count(fd))
168 {
169 _handleCgiPipeEvent(fd, events);
170 continue;
171 }
172 if (events & (EPOLLHUP | EPOLLERR))
173 {
174 if (!_listenFds.count(fd))
175 _dropConnection(fd);
176 continue;
177 }
178 if (_listenFds.count(fd))
179 {
181 continue;
182 }
183 std::map<int, Connection*>::iterator it = _connections.find(fd);
184 if (it == _connections.end())
185 continue;
186 _handleConnection(it->second, events);
187 }
188 }
189 std::cout << "\nServer shut down.\n";
190}
191
193{
194 int fd = conn->getFd();
195
196 try
197 {
198 if (events & EPOLLIN)
199 {
200 conn->handleRead();
201 if (conn->getState() == PROCESSING)
202 {
204 _enqueueProcessing(conn);
205 }
206 }
207 if ((events & EPOLLOUT) && conn->getState() == WRITING)
208 conn->handleWrite();
209 }
210 catch (const ClientException& e)
211 {
212 std::cerr << "client runtime error on fd " << fd << ": " << e.what() << std::endl;
213 conn->triggerError(e.getStatusCode());
214 }
215 catch (const FatalException&)
216 {
217 throw;
218 }
219 catch (const std::exception& e)
220 {
221 std::cerr << "unexpected runtime error on fd " << fd << ": " << e.what() << std::endl;
222 conn->triggerError(500);
223 }
224
225 //epoll based on final state for next iteration
226 switch (conn->getState())
227 {
228 case READING:
229 addPollFd(fd, EPOLLIN);
230 break;
231 case PROCESSING:
232 addPollFd(fd, 0);
233 break;
234 case WRITING:
235 addPollFd(fd, EPOLLIN | EPOLLOUT);
236 break;
237 case WAITING_FOR_CGI:
238 // Client fd is idle while CGI runs; pipe fd handles I/O
239 addPollFd(fd, 0);
240 break;
241 case FINISHED:
242 _dropConnection(fd);
243 break;
244 }
245}
246
247void ServerManager::addPollFd(int fd, uint32_t events)
248{
249 struct epoll_event ev;
250 std::memset(&ev, 0, sizeof(ev));
251 ev.events = events;
252 ev.data.fd = fd;
253
254 std::map<int, uint32_t>::iterator it = _fdEvents.find(fd);
255 if (it != _fdEvents.end())
256 {
257 it->second = events;
258 if (epoll_ctl(_epollFd, EPOLL_CTL_MOD, fd, &ev) < 0)
259 throw FatalException(std::string("epoll_ctl(MOD): ") + strerror(errno));
260 return;
261 }
262
263 if (epoll_ctl(_epollFd, EPOLL_CTL_ADD, fd, &ev) < 0)
264 throw FatalException(std::string("epoll_ctl(ADD): ") + strerror(errno));
265 _fdEvents[fd] = events;
266}
267
269{
270 struct sockaddr_in localAddr;
271 socklen_t len = sizeof(localAddr);
272 if (getsockname(clientFd, reinterpret_cast<struct sockaddr*>(&localAddr), &len) < 0)
273 return NULL;
274
275 std::map<struct sockaddr_in, const ServerConf*, SockAddrCompare>::const_iterator it;
276 it = _interfacePortPairs.find(localAddr);
277 if (it != _interfacePortPairs.end())
278 return it->second;
279 return NULL;
280}
281
282// --- Private Helpers ---
283
284int ServerManager::_createListeningSocket(const struct sockaddr_in& addr)
285{
286 int fd = socket(AF_INET, SOCK_STREAM, 0);
287 if (fd < 0)
288 throw FatalException(std::string("socket(): ") + strerror(errno));
289
290 int yes = 1;
291 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0)
292 {
293 close(fd);
294 throw FatalException(std::string("setsockopt(): ") + strerror(errno));
295 }
296
297 if (bind(fd, reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)) < 0)
298 {
299 close(fd);
300 throw FatalException(std::string("bind(): ") + strerror(errno));
301 }
302
303 if (listen(fd, BACKLOG) < 0)
304 {
305 close(fd);
306 throw FatalException(std::string("listen(): ") + strerror(errno));
307 }
308
309 if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
310 {
311 close(fd);
312 throw FatalException(std::string("fcntl(): ") + strerror(errno));
313 }
314
315 return fd;
316}
317
319{
320 while (true)
321 {
322 struct sockaddr_in clientAddr;
323 socklen_t clientLen = sizeof(clientAddr);
324 int clientFd = accept(listenFd,
325 reinterpret_cast<struct sockaddr*>(&clientAddr), &clientLen);
326 if (clientFd < 0)
327 {
328 if (errno == EAGAIN || errno == EWOULDBLOCK)
329 break;
330 std::cerr << "accept(): " << strerror(errno) << std::endl;
331 break;
332 }
333
334 if (fcntl(clientFd, F_SETFL, O_NONBLOCK) < 0)
335 {
336 std::cerr << "fcntl(client): " << strerror(errno) << std::endl;
337 close(clientFd);
338 continue;
339 }
340
341 const ServerConf* conf = NULL;
342 std::map<int, const ServerConf*>::const_iterator confIt = _listenFdToServerConf.find(listenFd);
343 if (confIt != _listenFdToServerConf.end())
344 conf = confIt->second;
345 Connection* conn = new Connection(clientFd, clientAddr, conf);
346 _connections[clientFd] = conn;
347 addPollFd(clientFd, EPOLLIN);
348
349 std::cout << "New connection from "
350 << req_utils::ipv4ToString(clientAddr) << ":"
351 << ntohs(clientAddr.sin_port)
352 << " [fd " << clientFd << "]" << std::endl;
353 }
354}
355
357{
358 char buf[RECV_BUFFER_SIZE];
359 ssize_t n = recv(fd, buf, sizeof(buf), 0);
360 if (n > 0)
361 {
362 std::cout << "\n--- Received " << n << " bytes from fd " << fd << " ---\n";
363 std::cout.write(buf, n);
364 std::cout << "\n--- End fd " << fd << " ---" << std::endl;
365 return true;
366 }
367 if (n == 0)
368 std::cout << "Client [fd " << fd << "] disconnected." << std::endl;
369 else
370 std::cerr << "recv error on fd " << fd << std::endl;
371 return false;
372}
373
375{
376 std::map<int, Connection*>::iterator it = _connections.find(fd);
377 if (it != _connections.end())
378 {
379 // Clean up any associated CGI pipe
380 int pipeFd = it->second->getCgiPipeFd();
381 if (pipeFd >= 0)
382 _unregisterCgiPipe(pipeFd);
383
384 _dequeueProcessing(it->second);
385 delete it->second;
386 _connections.erase(it);
387 }
388
389 epoll_ctl(_epollFd, EPOLL_CTL_DEL, fd, NULL);
390 close(fd);
391 _fdEvents.erase(fd);
392}
393
395{
396 if (_processingSet.insert(conn).second)
397 _processingQueue.push_back(conn);
398}
399
401{
402 _processingSet.erase(conn);
403}
404
406{
407 size_t budget = BACKLOG / 2;
408 size_t processed = 0;
409
410 while (!_processingQueue.empty() && processed < budget)
411 {
412 Connection* conn = _processingQueue.front();
413 _processingQueue.pop_front();
414
415 //skip connections removed by _dropConnection
416 if (_processingSet.find(conn) == _processingSet.end())
417 continue;
418
419 try
420 {
421 conn->process();
422 }
423 catch (const ClientException& e)
424 {
425 std::cerr << "client runtime error on fd " << conn->getFd() << ": " << e.what() << std::endl;
426 conn->triggerError(e.getStatusCode());
427 }
428 catch (const FatalException&)
429 {
430 throw;
431 }
432 catch (const std::exception& e)
433 {
434 std::cerr << "unexpected runtime error on fd " << conn->getFd() << ": " << e.what() << std::endl;
435 conn->triggerError(500);
436 }
437 ++processed;
438
439 if (conn->getState() == PROCESSING)
440 _processingQueue.push_back(conn);
441 else
442 {
443 _processingSet.erase(conn);
444 _finalizeProcessed(conn);
445 }
446 }
447}
448
450{
451 int fd = conn->getFd();
452 switch (conn->getState())
453 {
454 case WRITING:
455 addPollFd(fd, EPOLLIN | EPOLLOUT);
456 break;
457 case WAITING_FOR_CGI:
458 // Unsubscribe client fd from epoll (it's idle during CGI)
459 addPollFd(fd, 0);
460 // Register the CGI pipe fd in epoll for reading
461 _registerCgiPipe(conn);
462 break;
463 case FINISHED:
464 _dropConnection(fd);
465 break;
466 default:
467 break;
468 }
469}
470
472{
473 int pipeFd = conn->getCgiPipeFd();
474 if (pipeFd < 0)
475 return;
476 _cgiPipeToConn[pipeFd] = conn;
477 _cgiStartTimes[pipeFd] = time(NULL);
478 addPollFd(pipeFd, EPOLLIN);
479}
480
482{
483 if (pipeFd < 0)
484 return;
485 epoll_ctl(_epollFd, EPOLL_CTL_DEL, pipeFd, NULL);
486 _fdEvents.erase(pipeFd);
487 _cgiPipeToConn.erase(pipeFd);
488 _cgiStartTimes.erase(pipeFd);
489}
490
491void ServerManager::_handleCgiPipeEvent(int pipeFd, uint32_t events)
492{
493 std::map<int, Connection*>::iterator it = _cgiPipeToConn.find(pipeFd);
494 if (it == _cgiPipeToConn.end())
495 return;
496
497 Connection* conn = it->second;
498 Response* resp = conn->getResponse();
499
500 bool done = false;
501 try
502 {
503 if (events & (EPOLLHUP | EPOLLERR))
504 done = true;
505 else if (events & EPOLLIN)
506 done = resp->readCgiOutput();
507 }
508 catch (const ClientException& e)
509 {
510 std::cerr << "client CGI runtime error on fd " << conn->getFd() << ": " << e.what() << std::endl;
511 conn->triggerError(e.getStatusCode());
512 _unregisterCgiPipe(pipeFd);
513 addPollFd(conn->getFd(), EPOLLIN | EPOLLOUT);
514 return;
515 }
516 catch (const FatalException&)
517 {
518 throw;
519 }
520 catch (const std::exception& e)
521 {
522 std::cerr << "unexpected CGI runtime error on fd " << conn->getFd() << ": " << e.what() << std::endl;
523 conn->triggerError(500);
524 _unregisterCgiPipe(pipeFd);
525 addPollFd(conn->getFd(), EPOLLIN | EPOLLOUT);
526 return;
527 }
528
529 if (done)
530 {
531 _unregisterCgiPipe(pipeFd);
532 resp->finalizeCgiResponse();
533 conn->setState(WRITING);
534 addPollFd(conn->getFd(), EPOLLIN | EPOLLOUT);
535 }
536}
537
539{
540 if (_cgiPipeToConn.empty())
541 return;
542
543 time_t now = time(NULL);
544 std::vector<int> toKill;
545
546 for (std::map<int, time_t>::iterator it = _cgiStartTimes.begin();
547 it != _cgiStartTimes.end(); ++it)
548 {
549 if (now - it->second >= CGI_TIMEOUT_S)
550 toKill.push_back(it->first);
551 }
552
553 for (size_t i = 0; i < toKill.size(); ++i)
554 {
555 int pipeFd = toKill[i];
556 std::map<int, Connection*>::iterator it = _cgiPipeToConn.find(pipeFd);
557 if (it == _cgiPipeToConn.end())
558 continue;
559
560 Connection* conn = it->second;
561 Response* resp = conn->getResponse();
562 const ServerConf* conf = conn->getServerConf();
563
564 _unregisterCgiPipe(pipeFd);
565
566 if (conf)
567 resp->cgiTimeout(*conf);
568 else
569 {
570 resp->setStatusCode("504");
571 resp->setResponsePhrase("Gateway Timeout");
572 }
573 conn->setState(WRITING);
574 addPollFd(conn->getFd(), EPOLLIN | EPOLLOUT);
575 }
576}
577
579{
580 static time_t lastSweep = 0;
581 time_t now = time(NULL);
582 if (now - lastSweep < 5) // sweep every 5 seconds at most.
583 return;
584 lastSweep = now;
585
586 std::vector<int> toDrop;
587 for (std::map<int, Connection*>::iterator it = _connections.begin();
588 it != _connections.end(); ++it)
589 {
590 if (it->second->hasTimedOut(CONNECTION_TIMEOUT_S))
591 toDrop.push_back(it->first);
592 }
593 for (size_t i = 0; i < toDrop.size(); ++i)
594 {
595 std::map<int, Connection*>::iterator it = _connections.find(toDrop[i]);
596 if (it != _connections.end())
597 {
598 it->second->triggerError(408); // Request Timeout
599 _dequeueProcessing(it->second);
600 //sets state as WRITING and mods epoll.
601 _finalizeProcessed(it->second);
602 }
603 }
604}
605
607{
608 _processingQueue.clear();
609 _processingSet.clear();
610 _cgiPipeToConn.clear();
611 _cgiStartTimes.clear();
612
613 for(std::map<int, Connection*>::iterator it = _connections.begin();
614 it != _connections.end(); ++it)
615 {
616 delete it->second;
617 }
618 _connections.clear();
619
620 for (std::map<int, uint32_t>::iterator it = _fdEvents.begin();
621 it != _fdEvents.end(); ++it)
622 {
623 close(it->first);
624 }
625 _fdEvents.clear();
626 _listenFds.clear();
627 _listenFdToServerConf.clear();
628 _eventBuffer.clear();
629 if (_epollFd >= 0)
630 close(_epollFd);
631 _epollFd = -1;
632}
@ PROCESSING
@ READING
@ FINISHED
@ WRITING
@ WAITING_FOR_CGI
volatile sig_atomic_t g_running
Definition main.cpp:18
#define RECV_BUFFER_SIZE
#define CGI_TIMEOUT_S
#define CONNECTION_TIMEOUT_S
#define BACKLOG
static void cleanupAllProcesses()
Cleans up all active CGI processes on server shutdown. Sends SIGTERM to all processes,...
Exception type for failures scoped to a single client request.
virtual const char * what() const
int getStatusCode() const
void handleWrite()
Writes data from the _writeBuffer (or Response) to the client socket using send()....
ConnectionState getState() const
Response * getResponse() const
void setState(ConnectionState state)
Request * getRequest() const
void process()
Executes routing logic, instantiates the Response, and prepares data for sending. Transitions state t...
int getFd() const
void triggerError(int statusCode)
Forces the connection into an error state, bypassing normal processing.
const ServerConf * getServerConf() const
int getCgiPipeFd() const
Returns the CGI output pipe fd, or -1 if no CGI is active.
void handleRead()
Reads data from the client socket using recv() into _readBuffer. Transitions state to PROCESSING if t...
void resetReadPosition()
Resets the internal read position to the beginning of the data.
DataStore & getBodyStore()
Returns a reference to the DataStore to allow direct writing from recv().
Definition Request.cpp:390
void setResponsePhrase(const std::string &phrase)
void finalizeCgiResponse()
Finalizes the CGI response after all output has been read. Parses CGI output headers,...
Definition Response.cpp:930
bool readCgiOutput()
Called by ServerManager when the CGI pipe is readable. Reads available data into _responseDataStore.
Definition Response.cpp:877
void cgiTimeout(const ServerConf &config)
Called by ServerManager when the CGI process times out. Kills the process and builds a 504 error page...
Definition Response.cpp:910
void setStatusCode(const std::string &code)
const struct sockaddr_in & getInterfacePortPair() const
const std::string & getServerName() const
void _enqueueProcessing(Connection *conn)
Adds a connection to the processing queue if not already queued.
void _handleCgiPipeEvent(int pipeFd, uint32_t events)
Handles an epoll event on a CGI pipe fd.
void _closeAllFds()
Closes all fds (listeners + clients) during shutdown.
std::set< int > _listenFds
void addServer(const ServerConf *conf)
Creates a listening socket for the given ServerConf's IP:Port and registers the mapping.
ServerManager & operator=(const ServerManager &other)
void _runRoundRobin()
Runs a budgeted round-robin pass over _processingQueue. Calls process() once per connection,...
const ServerConf * getServerConfForFd(int clientFd) const
Uses getsockname() to find the local address of a client fd, then returns the matching ServerConf.
void _finalizeProcessed(Connection *conn)
Handles state transition for a connection that just left PROCESSING.
void _unregisterCgiPipe(int pipeFd)
Unregisters a CGI pipe fd from epoll and removes the mapping.
std::map< struct sockaddr_in, const ServerConf *, SockAddrCompare > _interfacePortPairs
std::set< Connection * > _processingSet
void _registerCgiPipe(Connection *conn)
Registers a CGI pipe fd in epoll and maps it to its Connection.
std::deque< Connection * > _processingQueue
void addPollFd(int fd, uint32_t events)
Adds or updates an fd in the epoll list (e.g., CGI output pipe).
std::vector< ServerConf * > _serverConfs
bool _readAndPrint(int fd)
Reads from a client fd and prints the raw data.
void _handleConnection(Connection *conn, uint32_t events)
Dispatches epoll events to the correct Connection handler and drives state transitions....
void _sweepTimeouts()
Scans all connections and drops any that have been idle too long.
void addListenPort(int port)
Temporary overload for early development: creates a listening socket on the given port bound to INADD...
std::map< int, time_t > _cgiStartTimes
std::map< int, uint32_t > _fdEvents
std::map< int, const ServerConf * > _listenFdToServerConf
void _acceptNewConnections(int listenFd)
Accepts all pending connections on a listening fd. Sets each client fd to O_NONBLOCK and adds it to _...
void _dequeueProcessing(Connection *conn)
Removes a connection from the processing set (queue cleanup is lazy).
std::map< int, Connection * > _cgiPipeToConn
std::vector< struct epoll_event > _eventBuffer
int _createListeningSocket(const struct sockaddr_in &addr)
Creates, binds, listens, and sets O_NONBLOCK on a socket.
void _dropConnection(int fd)
Closes a client fd and removes it from epoll and _connections.
void run()
Enters the main epoll() event loop. Blocks until g_running becomes false.
void _sweepCgiTimeouts()
Sweeps CGI processes for timeout.
std::map< int, Connection * > _connections
volatile sig_atomic_t g_running
Definition main.cpp:18
std::string ipv4ToString(const struct ::sockaddr_in &addr)
Definition Request.cpp:29