LeftHookRoll
An HTTP/1.0 compliant web server, as specified by RFC1945
Loading...
Searching...
No Matches
Connection.cpp
Go to the documentation of this file.
1#include "../includes/Connection.hpp"
2#include "../includes/ServerConf.hpp"
3#include <iostream>
4#include <cstring>
5#include <cerrno>
6#include <sys/socket.h>
7#include <arpa/inet.h>
8#include <unistd.h>
9#include <sstream>
10#include "../includes/FatalExceptions.hpp"
11
12// --- Canonical Form ---
13
15 : _acceptFD(-1),
16 _lastActivity(time(NULL)),
17 _serverConf(NULL),
18 _locationConf(NULL),
19 _readBufferSize(MAX_HEADER_SIZE),
20 _request(NULL),
21 _response(NULL),
22 _writeBufferSize(0),
23 _state(READING),
24 _totalBytesRead(0)
25{
26 std::memset(&_IPA, 0, sizeof(_IPA));
27 _request = new Request(0);
28 _response = new Response();
29}
30
31Connection::Connection(int fd, const struct sockaddr_in& ipa, const ServerConf* defaultConfig)
32 : _acceptFD(fd),
33 _IPA(ipa),
34 _lastActivity(time(NULL)),
35 _serverConf(defaultConfig),
36 _locationConf(NULL),
37 _readBufferSize(MAX_HEADER_SIZE),
38 _request(NULL),
39 _response(NULL),
40 _writeBufferSize(0),
41 _state(READING),
42 _totalBytesRead(0)
43{
44 long long maxBody = 0;
45 if (_serverConf)
46 maxBody = static_cast<long long>(_serverConf->getMaxBodySize());
47 _request = new Request(maxBody);
48 _response = new Response();
49}
50
52 : _acceptFD(other._acceptFD),
53 _IPA(other._IPA),
54 _lastActivity(other._lastActivity),
55 _serverConf(other._serverConf),
56 _locationConf(other._locationConf),
57 _readBufferSize(other._readBufferSize),
58 _readBuffer(other._readBuffer),
59 _request(NULL),
60 _response(NULL),
61 _writeBufferSize(other._writeBufferSize),
62 _writeBuffer(other._writeBuffer),
63 _state(other._state),
64 _totalBytesRead(other._totalBytesRead)
65{
66 _request = new Request(*other._request);
67 _response = new Response(*other._response);
68}
69
71{
72 if (this != &other)
73 {
74 _acceptFD = other._acceptFD;
75 _IPA = other._IPA;
83 _state = other._state;
85
86 *_request = *other._request;
87 *_response = *other._response;
88 }
89 return *this;
90}
91
93{
94 delete _request;
95 delete _response;
96}
97
98// --- Getters & Setters ---
99int Connection::getFd() const { return _acceptFD; }
106
108{
109 if (_response && _response->getCgiOutputFd() >= 0)
110 return _response->getCgiOutputFd();
111 return -1;
112}
113// --- Private Helpers ---
115{
116 _lastActivity = time(NULL);
117}
118
119void Connection::_readHeaders(const char* buf, size_t n)
120{
121 _readBuffer.append(buf, n);
122 if (_readBuffer.size() > MAX_HEADER_SIZE)
123 {
124 triggerError(431); // Request header too large.
125 return;
126 }
127
128 size_t headerEnd = _request->parseHeaders(_readBuffer);
130 {
131 triggerError(std::atoi(_request->getStatusCode().c_str()));
132 return;
133 }
135 return;
136
137 //saving body data that made its way into the buffer.
138 std::string leftover = _readBuffer.substr(headerEnd);
139 _readBuffer.clear();
140
141 ReqState rState = _request->getReqState();
142
143 if (rState == REQ_DONE)
144 {
146 return;
147 }
148
149 if (!leftover.empty())
150 {
151 _request->getBodyStore().append(leftover);
152 if (rState == REQ_CHUNKED && _request->isChunkedDone(leftover))
153 {
155 return;
156 }
157 if (rState == REQ_BODY && _request->getBodyStore().getSize() >= static_cast<size_t>(_request->getContentLength()))
158 {
160 return;
161 }
162 }
163}
164
165void Connection::_readBody(const char* buf, size_t n)
166{
167 _request->getBodyStore().append(buf, n);
168 if (_request->getBodyStore().getSize() >= static_cast<size_t>(_request->getContentLength()))
170}
171
172void Connection::_readChunked(const char* buf, size_t n)
173{
174 std::string chunk(buf, n);
175 _request->getBodyStore().append(chunk);
176 if (_request->isChunkedDone(chunk))
178}
179// --- State Machine Actions ---
181{
182 if (_state != READING)
183 return;
184
185 char buf[MAX_HEADER_SIZE];
186 ssize_t n = recv(_acceptFD, buf, sizeof(buf), 0);
187 if (n <= 0)
188 {
189 if (n < 0)
190 std::cerr << "recv error on client " << req_utils::ipv4ToString(_IPA) <<
191 ": " << strerror(errno) << std::endl;
193 return;
194 }
195
197 _totalBytesRead += static_cast<size_t>(n);
198
199 ReqState rState = _request->getReqState();
200 size_t len = static_cast<size_t>(n);
201
202 if (rState == REQ_HEADERS)
203 _readHeaders(buf, len);
204 else if (rState == REQ_BODY)
205 _readBody(buf, len);
206 else if (rState == REQ_CHUNKED)
207 _readChunked(buf, len);
208}
209
211{
212 if (_state != PROCESSING)
213 return;
214
216
217 try
218 {
220 {
222 return; // still decoding chunked encoding.
223 }
224 //if anything went wrong at all;recheck that 200 is indeed the default in yaman's implemenation.
225 if (_request->getStatusCode() != "200")
226 {
227 triggerError(std::atoi(_request->getStatusCode().c_str()));
228 return;
229 }
230
231 if (_serverConf)
232 {
234 {
235 // Check if this is a CGI request that needs pipe monitoring
237 {
239 return;
240 }
241 return; // still in round-robin (e.g. POST writing)
242 }
243 }
244 else
245 {
246 //noconf fallback
247 triggerError(500);
248 return;
249 }
250 _state = WRITING;
251 }
252 catch (const ClientException& e)
253 {
254 std::cerr << "client runtime error on fd " << _acceptFD << ": " << e.what() << std::endl;
256 }
257 catch (const FatalException&)
258 {
259 throw;
260 }
261 catch (const std::exception& e)
262 {
263 std::cerr << "unexpected runtime error on fd " << _acceptFD << ": " << e.what() << std::endl;
264 triggerError(500);
265 }
266}
267
269{
270 if (_state != WRITING)
271 return;
275}
276
277// --- Error & Timeout Management ---
278
279bool Connection::hasTimedOut(int timeoutSeconds) const
280{
281 return (time(NULL) - _lastActivity) >= timeoutSeconds;
282}
283
284void Connection::triggerError(int statusCode)
285{
286 std::ostringstream oss;
287 oss << statusCode;
288
289 if (_serverConf)
291 else
292 {
293 // noconf fallback
294 _response->setStatusCode(oss.str());
296 }
297
298 _state = WRITING;
299}
#define MAX_HEADER_SIZE
Definition Connection.hpp:8
ConnectionState
Represents the state of the CLIENT SOCKET.
@ PROCESSING
@ READING
@ FINISHED
@ WRITING
@ WAITING_FOR_CGI
ReqState
Represents the current network-reading phase of the HTTP request.
Definition Request.hpp:27
@ REQ_BODY
Definition Request.hpp:29
@ REQ_ERROR
Definition Request.hpp:32
@ REQ_CHUNKED
Definition Request.hpp:30
@ REQ_DONE
Definition Request.hpp:31
@ REQ_HEADERS
Definition Request.hpp:28
@ BUILD_CGI_RUNNING
Definition Response.hpp:39
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()....
void setLocationConf(const LocationConf *conf)
Assigns the appropriate location block after parsing the request URI.
ConnectionState _state
ConnectionState getState() const
std::string _writeBuffer
Connection & operator=(const Connection &other)
Response * getResponse() const
size_t _writeBufferSize
const ServerConf * _serverConf
bool hasTimedOut(int timeoutSeconds) const
Checks if the connection has exceeded the defined timeout threshold.
void setState(ConnectionState state)
struct sockaddr_in _IPA
std::string _readBuffer
Request * _request
Response * _response
Request * getRequest() const
void process()
Executes routing logic, instantiates the Response, and prepares data for sending. Transitions state t...
void _updateActivityTimer()
Updates the _lastActivity timestamp to current time.
const LocationConf * _locationConf
int getFd() const
void triggerError(int statusCode)
Forces the connection into an error state, bypassing normal processing.
const ServerConf * getServerConf() const
time_t _lastActivity
size_t _readBufferSize
void _readChunked(const char *buf, size_t n)
int getCgiPipeFd() const
Returns the CGI output pipe fd, or -1 if no CGI is active.
void _readHeaders(const char *buf, size_t n)
void handleRead()
Reads data from the client socket using recv() into _readBuffer. Transitions state to PROCESSING if t...
size_t _totalBytesRead
void _readBody(const char *buf, size_t n)
size_t getSize() const
Gets total bytes currently stored (whether in RAM or File).
void append(const char *data, size_t length)
Appends raw byte data to the store. Automatically transitions from RAM to FILE_MODE if _bufferLimit i...
bool isChunkedDone(const std::string &newData) const
Checks if the incoming chunked data contains the terminal "\r\n\r\n". Used to transition from REQ_CHU...
Definition Request.cpp:359
DataStore & getBodyStore()
Returns a reference to the DataStore to allow direct writing from recv().
Definition Request.cpp:390
ReqState getReqState() const
Definition Request.cpp:367
std::string getStatusCode() const
Definition Request.cpp:370
size_t parseHeaders(const std::string &rawBuffer)
Parses the raw buffer containing the HTTP Request-Line and Headers. Transitions state to REQ_BODY,...
Definition Request.cpp:168
long long getContentLength() const
Definition Request.cpp:379
bool processBodySlice()
Unified method to prepare the body for the Response/CGI phase. For Content-Length bodies,...
Definition Request.cpp:302
BuildPhase getBuildPhase() const
Definition Response.cpp:867
void setResponsePhrase(const std::string &phrase)
void buildErrorPage(const std::string &code, const ServerConf &config)
Fast-tracks the response to an error state. Loads the appropriate error page from config or default H...
Definition Response.cpp:364
bool buildResponse(Request &req, const ServerConf &config)
Analyzes the Request and Location settings to prepare the response. Sets the status code,...
Definition Response.cpp:298
int getCgiOutputFd() const
Returns the CGI output pipe fd for epoll registration.
Definition Response.cpp:870
bool sendSlice(int fd)
Sends a slice of the response to the client socket. To be called during a POLLOUT event.
Definition Response.cpp:409
void setStatusCode(const std::string &code)
size_t getMaxBodySize() const
std::string ipv4ToString(const struct ::sockaddr_in &addr)
Definition Request.cpp:29