Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
#include <iostream>
#include <vector>
#include <cstring>
#include <sstream>
#include <regex>
#include <chrono>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include "threadpool.hpp"
#include "httpd.hpp"
bool parse_requestline_into(const std::string &head_text, HttpRequest &req)
{
// Find the end of the request line
auto offset_httpver_end = head_text.find("\r\n");
if (offset_httpver_end == std::string::npos)
return false;
// Find the end of the first section (METHOD) and the beginning of the next (URI)
auto offset_method_end = head_text.find(" ", 0);
if (offset_method_end == std::string::npos || offset_method_end > offset_httpver_end)
return false;
std::string method = head_text.substr(0, offset_method_end);
// Find the space after the URI and before the HTTPVER
auto offset_uri_end = head_text.find(" ", offset_method_end+1);
if (offset_uri_end == std::string::npos || offset_uri_end > offset_httpver_end)
return false;
std::string uri = head_text.substr(offset_method_end+1, offset_uri_end-offset_method_end -1);
std::string httpver = head_text.substr(offset_uri_end+1, offset_httpver_end-offset_uri_end -1);
req._method = method;
req._uri = uri;
req._httpver = httpver;
return true;
}
bool parse_headers_into(const std::string &head_text, HttpRequest &req)
{
// The first call gets the end of the request line
auto offset = head_text.find("\r\n");
// Loop as long as there are more lines to parse
while (offset < head_text.size()-1 -2)
{
// Skip the 2 characters from \r\n
offset += 2;
// Search the next ": " divider, starting from the current line beginning
auto offset_colon = head_text.find(": ", offset);
if (offset_colon == std::string::npos)
return false;
auto header_key = head_text.substr(offset, offset_colon - offset);
offset = head_text.find("\r\n", offset_colon);
if (offset == std::string::npos)
return false;
auto header_val = head_text.substr(offset_colon+2, offset - offset_colon - 2);
req._headers.setHeader(header_key, header_val);
}
return true;
}
void HttpServer::handleConnection(int sockfd, const std::string &ip, uint16_t port)
{
// HttpRequest object will be filled with the parsed request parameters
HttpRequest req;
req._ip = ip;
req._port = port;
// Temporary read buffer that is used as an immediate target for receiving data
std::vector<uint8_t> buff(tcp_read_buffer_size);
// Body buffer that will be filled with accidentally read body data
std::vector<uint8_t> body_buff;
// String buffer that will be filled with the head data (request line + headers)
std::string head_str_buff;
// Byte offset at which the head ends (the location of the head-end \r\n\r\n )
// The body content begins at offset_head_end + 4
size_t offset_head_end = std::string::npos;
// The number of bytes read with the last read command
ssize_t bytes_read;
// Loop and read from socket while the head is not finished and there is still data available
while (offset_head_end == std::string::npos && (bytes_read = read(sockfd, buff.data(), tcp_read_buffer_size)) > 0)
{
// Append the newly read data to the head string buffer
head_str_buff.append((char*)buff.data(), bytes_read);
// Look for the head end. The loop will end if it is found
offset_head_end = head_str_buff.find("\r\n\r\n");
}
// Cut off the head-end, as well as body data that was possibly read
// One line end (\r\n) is left in for easier header parsing
head_str_buff.resize(offset_head_end + 2);
// Try to parse the request line (METHOD URI HTTPVER)
if (!parse_requestline_into(head_str_buff, req))
{
char resp[] = "HTTP/1.1 400 Bad Request\r\n\r\n";
send(sockfd, resp, sizeof(resp), 0);
return;
}
// Try to parse the headers
if (!parse_headers_into(head_str_buff, req))
{
char resp[] = "HTTP/1.1 400 Bad Request\r\n\r\n";
send(sockfd, resp, sizeof(resp), 0);
return;
}
// Insert potential body data into the body buffer. Body data might accidently be read
// when the head-end (\r\n\r\n) is somewhere in the middle of the read buffer.
// This does not read the full body data, instead only data that was already read is
// inserted
if (offset_head_end < head_str_buff.size()-1)
{
body_buff.insert(body_buff.end(), buff.begin() + offset_head_end + 4, buff.begin() + bytes_read);
}
// Prepare HttpResponse
HttpResponse res(sockfd);
// Try to match routes available for the server using the dedicated matching type
// for each available route.
bool finalHandled = false;
for (const auto &route : routes)
{
bool match_found = false;
std::smatch matches;
switch (route.matchType)
{
case HttpRoute::MatchType::MatchAny:
match_found = true;
break;
case HttpRoute::MatchType::Regex:
if (std::regex_search(req._uri, matches, route.route_matcher))
{
for (auto m : matches)
{
req._regexMatches.push_back(m);
}
match_found = true;
}
break;
case HttpRoute::MatchType::Literal:
if (route.route == req._uri)
{
match_found = true;
}
break;
case HttpRoute::MatchType::StartsWith:
if (req._uri.compare(0, route.route.size(), route.route) == 0)
{
match_found = true;
}
break;
}
if (match_found)
{
auto routeType = route.handler_fn(req, res);
if (routeType == HttpRouteHandling::End)
{
finalHandled = true;
break;
}
}
}
// If none of the routes match, use the default handler. This will cause a
// 404 Not found status code by default
if (!finalHandled) defaultHandler(req, res);
}
HttpRouteHandling HttpServer::defaultHandlerFunction(const HttpRequest &req, HttpResponse &res)
{
res.sendDefault404();
return HttpRouteHandling::End;
}
HttpServer::HttpServer(uint16_t port, std::string ip)
: port{port}, ip{ip}
{
// Try to convert the given ipv4 string into the C lower level ipv4 struct
if (inet_aton(ip.c_str(), &ip_inaddr) == 0)
{
throw HttpException(HttpException::InvalidIP);
}
}
void HttpServer::serveForever()
{
// The number of connections that the server can keep on wait (not yet accepted) before
// refusing further connection requests
const int waiting_connections = pendingConnections;
// Socket File Descriptor for listening
int sockfd_listen;
// Socket Address for listening
sockaddr_in local_saddr = {0};
local_saddr.sin_family = AF_INET;
local_saddr.sin_addr.s_addr = ip_inaddr.s_addr;
local_saddr.sin_port = htons(port);
try
{
// Create a tcp network socket
sockfd_listen = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd_listen < 0)
{
throw(HttpException(
HttpException::SocketOpen,
"Opening socket failed"
));
}
// Bind the socket to the listening address and port
if (bind(sockfd_listen, (sockaddr*) &local_saddr, sizeof(local_saddr)))
{
throw(HttpException(
HttpException::SocketBind,
"Binding socket failed"
));
}
// Listen to new connections
listen(sockfd_listen, waiting_connections);
// Create and run the threadpool
Threadpool tp(numberOfThreads, queuedConnections);
// Accept-Handle-Repeat loop
// This loops forever and handles new requests
while (true)
{
// Remote socket address
sockaddr_in remote_saddr = {0};
// Length of the remote socket address structure
socklen_t remote_slen = sizeof(remote_saddr);
// Accept a new tcp connection
int remote_sockfd = accept(sockfd_listen, (sockaddr*) &remote_saddr, &remote_slen);
if (remote_sockfd < 0)
{
throw(HttpException(
HttpException::TcpAccept,
"Accepting connection failed"
));
}
//std::cout << "Connection opened\n";
// Pass the tcp connection socket to the http handling function
// handleConnection(remote_sockfd);
tp.addTask([this, remote_sockfd, remote_saddr]() {
try
{
// inet_ntoa returns the string in a statically allocated buffer
// that will be overwritten on subsequent calls. So no free needed
std::string remote_ip(inet_ntoa(remote_saddr.sin_addr));
uint16_t remote_port = ntohs(remote_saddr.sin_port);
handleConnection(remote_sockfd, remote_ip, remote_port);
close(remote_sockfd);
}
catch (const HttpException& e)
{
close(remote_sockfd);
std::cerr << e.what() + '\n';
}
});
//std::cout << "Connection closing\n";
// Close the accepted tcp connection
// close(remote_sockfd);
}
}
catch (const HttpException& e)
{
close(sockfd_listen);
throw e;
}
}