· 9 years ago · Dec 14, 2016, 08:22 AM
1#include <iostream>
2#include <cstring>
3#include <cstdio>
4#include <sstream>
5#include <string>
6#include <map>
7#include <utility>
8#include <algorithm>
9#include <memory>
10#include <vector>
11#include <fstream>
12#include <streambuf>
13// -- linux only
14
15#include <unistd.h>
16#include <sys/types.h>
17#include <sys/socket.h>
18#include <arpa/inet.h>
19#include <sys/stat.h>
20
21#include <sqlite3.h>
22
23#define MAXLEN 10000
24#define PORT 45678
25#define FILE_CHUNK 64*1024
26#define DB_NAME "people.db"
27
28#define MIN(x, y) ((x) < (y) ? (x) : (y))
29
30bool fileExists(const std::string &filepath) {
31 struct stat fileInfo;
32 bool exists = stat(filepath.c_str(), &fileInfo) == 0;
33 return exists;
34}
35
36std::string readFile(const std::string &path) {
37 std::ifstream t(path);
38 std::string str((std::istreambuf_iterator<char>(t)),
39 std::istreambuf_iterator<char>());
40 return str;
41}
42
43struct Human {
44 std::string m_Name;
45 std::string m_Surname;
46};
47
48static int selectHumansCallback(void *data, int argc, char **argv, char **azColName) {
49 std::vector<Human> *list = (std::vector<Human>*)data;
50
51 Human h;
52 h.m_Name = std::string(argv[1]);
53 h.m_Surname = std::string(argv[2]);
54
55 list->push_back(h);
56
57 return 0;
58}
59
60class DBManager {
61public:
62 DBManager() {
63 m_DatabaseExisted = fileExists(DB_NAME);
64 }
65
66 ~DBManager() {
67 sqlite3_close(db);
68 }
69
70public:
71 void initialize() {
72 openDB();
73 if (!m_DatabaseExisted) {
74 createTables();
75 fillWithFakeData();
76 }
77 }
78
79 void createHuman(const std::string &name, const std::string &surname) {
80 char *zErrMsg = 0;
81
82 char sql[1000];
83 memset(sql, 0, sizeof(sql));
84
85 snprintf(sql, sizeof(sql), "INSERT INTO Humans (Name, Surname) VALUES ('%s', '%s')", name.c_str(), surname.c_str());
86
87 int rc = sqlite3_exec(db, sql, 0/*callback*/, 0, &zErrMsg);
88 if (rc != SQLITE_OK) {
89 fprintf(stderr, "SQL error: %s\n", zErrMsg);
90 sqlite3_free(zErrMsg);
91 } else {
92 fprintf(stdout, "Human created\n");
93 }
94 }
95
96 std::vector<Human> getAllHumans() {
97 char *zErrMsg = 0;
98 const char *sql = "SELECT * FROM Humans";
99 std::vector<Human> list;
100
101 int rc = sqlite3_exec(db, sql, selectHumansCallback, (void*)&list, &zErrMsg);
102 if (rc != SQLITE_OK) {
103 fprintf(stderr, "SQL error: %s\n", zErrMsg);
104 sqlite3_free(zErrMsg);
105 } else {
106 fprintf(stdout, "All humans retrieved\n");
107 }
108
109 std::cout << list.size() << " humans retrieved" << std::endl;
110
111 return list;
112 }
113
114private:
115 bool openDB() {
116 int rc;
117 rc = sqlite3_open(DB_NAME, &db);
118 if (rc) {
119 fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
120 } else {
121 fprintf(stdout, "Opened database successfully\n");
122 }
123
124 return rc == 0;
125 }
126
127 void createTables() {
128 int rc;
129 char *zErrMsg = 0;
130
131 const char *sql = ""
132 "CREATE TABLE Humans("
133 "ID INT PRIMARY KEY,"
134 "Name TEXT NOT NULL,"
135 "Surname TEXT NOT NULL);"
136 ""
137 "CREATE TABLE Employers("
138 "ID INT PRIMARY KEY,"
139 "JobTitle TEXT NOT NULL,"
140 "Human_ID INT NOT NULL,"
141 "FOREIGN KEY(Human_ID) REFERENCES Humans(ID));"
142 ""
143 "CREATE TABLE Salaries("
144 "ID INT PRIMARY KEY,"
145 "Salary INT NOT NULL,"
146 "Employer_ID INT NOT NULL,"
147 "FOREIGN KEY(Employer_ID) REFERENCES Employers(ID));";
148
149 rc = sqlite3_exec(db, sql, /*callback*/0, 0, &zErrMsg);
150 if (rc != SQLITE_OK) {
151 fprintf(stderr, "SQL error: %s\n", zErrMsg);
152 sqlite3_free(zErrMsg);
153 } else {
154 fprintf(stdout, "Tables created successfully\n");
155 }
156 }
157
158 void fillWithFakeData() {
159 char *zErrMsg = 0;
160
161 const char *sql = "INSERT INTO Humans (ID,Name,Surname) "
162 "VALUES (1, 'Paul', 'Dubs'); "
163 "Insert INTO Humans (ID,Name,Surname) "
164 "VALUES (2, 'Allen','Clark'); "
165 "INSERT INTO Humans (ID,Name,Surname) "
166 "VALUES (3, 'Teddy', 'Fray');"
167 "INSERT INTO Humans (ID,Name,Surname) "
168 "VALUES (4, 'Mark', 'Rich');"
169 "INSERT INTO Employers (ID,JobTitle,Human_ID) "
170 "VALUES (1, 'Cleaner', 2); "
171 "INSERT INTO Employers (ID,JobTitle,Human_ID) "
172 "VALUES (2, 'Manager', 4); "
173 "INSERT INTO Employers (ID,JobTitle,Human_ID) "
174 "VALUES (3, 'Developer', 1);"
175 "INSERT INTO Employers (ID,JobTitle,Human_ID) "
176 "VALUES (4, 'Designer', 3);"
177 "INSERT INTO Salaries (ID,Salary,Employer_ID) "
178 "VALUES (1, 23000, 2); "
179 "INSERT INTO Salaries (ID,Salary,Employer_ID) "
180 "VALUES (2, 32000, 1); "
181 "INSERT INTO Salaries (ID,Salary,Employer_ID) "
182 "VALUES (3, 50000, 3);"
183 "INSERT INTO Salaries (ID,Salary,Employer_ID) "
184 "VALUES (4, 42000, 4);";
185
186 int rc = sqlite3_exec(db, sql, /*callback*/0, 0, &zErrMsg);
187 if (rc != SQLITE_OK) {
188 fprintf(stderr, "SQL error: %s\n", zErrMsg);
189 sqlite3_free(zErrMsg);
190 } else {
191 fprintf(stdout, "Records created successfully\n");
192 }
193 }
194
195private:
196 bool m_DatabaseExisted;
197 sqlite3 *db;
198};
199
200class IActionResult {
201public:
202 virtual ~IActionResult() {}
203 virtual std::string prepareResponse() = 0;
204 virtual bool shouldRedirect() = 0;
205 virtual std::string getRedirectUrl() = 0;
206};
207
208class IActionHandler {
209public:
210 virtual ~IActionHandler() {}
211 virtual std::shared_ptr<IActionResult> execAction(const std::string ¶ms) = 0;
212};
213
214std::map<std::string, std::string> Router;
215std::map<std::string, std::string> ResourceTypes;
216std::map<std::string, std::shared_ptr<IActionHandler> > ActionHandlers;
217std::string ServedDirectory;
218DBManager dbManager;
219
220void initRouter() {
221 Router.insert(std::make_pair("/", "index.html"));
222}
223
224void initTypes() {
225 ResourceTypes.insert(std::make_pair("text", "text/html"));
226 ResourceTypes.insert(std::make_pair("html", "text/html"));
227 ResourceTypes.insert(std::make_pair("css", "text/css"));
228 ResourceTypes.insert(std::make_pair("zip", "application/zip"));
229 ResourceTypes.insert(std::make_pair("jpg", "image/jpeg"));
230 ResourceTypes.insert(std::make_pair("jpeg", "image/jpeg"));
231 ResourceTypes.insert(std::make_pair("gif", "image/gif"));
232 ResourceTypes.insert(std::make_pair("png", "image/png"));
233 ResourceTypes.insert(std::make_pair("js", "text/javascript"));
234 ResourceTypes.insert(std::make_pair("xml", "text/xml"));
235}
236
237class CreateHumanResult: public IActionResult {
238public:
239 CreateHumanResult(const std::string &url): m_RedirectUrl(url) {}
240 virtual std::string prepareResponse() { return ""; }
241 virtual bool shouldRedirect() { return true; }
242 virtual std::string getRedirectUrl() { return m_RedirectUrl; };
243private:
244 std::string m_RedirectUrl;
245};
246
247class AllHumansResult: public IActionResult {
248public:
249 AllHumansResult(const std::vector<Human> &list):
250 m_HumansList(list)
251 {}
252
253private:
254 std::string readTemplate() {
255 // open allusers.mytemplate
256 return readFile("allhumans.mytemplate");
257 }
258
259public:
260 virtual bool shouldRedirect() { return false; }
261 virtual std::string getRedirectUrl() { return ""; };
262
263 virtual std::string prepareResponse() {
264 std::string response;
265
266 for (auto &h: m_HumansList) {
267 response += "<div>" + h.m_Name + " - " + h.m_Surname + "</div>" + "\r\n";
268 }
269
270 std::string responseTemplate = readTemplate();
271 const std::string templateVar = "$TEMPLATE_CONTENTS";
272 size_t pos = responseTemplate.find(templateVar);
273
274 if (pos != std::string::npos) {
275 responseTemplate.replace(pos, templateVar.length(), response);
276 }
277
278 return responseTemplate;
279 }
280
281private:
282 std::vector<Human> m_HumansList;
283};
284
285class CreateHumanActionHandler: public IActionHandler {
286public:
287 CreateHumanActionHandler(DBManager *dbManager):
288 m_DBManager(dbManager)
289 {}
290
291private:
292 void parseParameters(const std::string ¶ms) {
293 auto n = params.find("&");
294 auto field1 = params.substr(0, n);
295 auto field2 = params.substr(n + 1);
296 auto nameStart = field1.find("=");
297 //column1 = field1.substr(0, nameStart);
298 m_Name = field1.substr(nameStart + 1);
299 auto surnameStart = field2.find("=");
300 //column2 = field2.substr(0, surnameStart);
301 m_Surname = field2.substr(surnameStart + 1);
302 }
303
304 bool validateParameters() {
305 return (!m_Name.empty()) && (!m_Surname.empty());
306 }
307
308 void createHumanInDatabase() {
309 m_DBManager->createHuman(m_Name, m_Surname);
310 }
311public:
312 virtual std::shared_ptr<IActionResult> execAction(const std::string ¶ms) {
313 parseParameters(params);
314
315 if (validateParameters()) {
316 createHumanInDatabase();
317 }
318
319 std::shared_ptr<IActionResult> result(new CreateHumanResult("/allhumans"));
320 return result;
321 }
322
323private:
324 DBManager *m_DBManager;
325 std::string m_Name;
326 std::string m_Surname;
327};
328
329class AllHumansActionHandler: public IActionHandler {
330public:
331 AllHumansActionHandler(DBManager *dbManager):
332 m_DBManager(dbManager)
333 {}
334
335public:
336 virtual std::shared_ptr<IActionResult> execAction(const std::string ¶ms) {
337 std::vector<Human> list = m_DBManager->getAllHumans();
338 std::shared_ptr<AllHumansResult> allUsers(new AllHumansResult(list));
339
340 std::shared_ptr<IActionResult> result = std::dynamic_pointer_cast<IActionResult>(allUsers);
341 return result;
342 }
343
344private:
345 DBManager *m_DBManager;
346};
347
348void initActionHandlers() {
349 std::shared_ptr<IActionHandler> createHandler(new CreateHumanActionHandler(&dbManager));
350 ActionHandlers.insert(std::make_pair("/createhuman", createHandler));
351
352 std::shared_ptr<IActionHandler> allHumansHandler(new AllHumansActionHandler(&dbManager));
353 ActionHandlers.insert(std::make_pair("/allhumans", allHumansHandler));
354}
355
356std::string getMimeType(const std::string &fileExt) {
357 std::string key = fileExt;
358 std::transform(key.begin(), key.end(), key.begin(), ::tolower);
359
360 auto it = ResourceTypes.find(fileExt);
361 if (it != ResourceTypes.end()) {
362 return it->second;
363 } else {
364 return "application/octet-stream";
365 }
366}
367
368inline bool isEndOfStream(const std::string &message) {
369 return (message.empty())
370 || (message == "\r\n")
371 || (message == "\r")
372 || (message == "\n");
373}
374
375// writes array of bytes to socket
376int sendData(int connfd, void *data, int datalen) {
377 unsigned char *pdata = (unsigned char *) data;
378 int numSent;
379
380 while (datalen > 0) {
381 numSent = ::send(connfd, pdata, datalen, 0);
382 if (numSent == -1) {
383 return -1;
384 }
385
386 pdata += numSent;
387 datalen -= numSent;
388 }
389
390 return 0;
391}
392
393int getFileSize(const std::string &filepath) {
394 struct stat fileInfo;
395 int fileSize = 0;
396
397 if (stat(filepath.c_str(), &fileInfo) == 0) {
398 fileSize = (int)fileInfo.st_size;
399 } else {
400 std::cout << "Cannot stat " << filepath << std::endl;
401 }
402
403 return fileSize;
404}
405
406// writes file to socket
407bool sendFile(int connfd, const std::string &fullpath) {
408 FILE *file = nullptr;
409 file = fopen(fullpath.c_str(), "r");
410 if (!file) {
411 return false;
412 }
413
414 char buffer[FILE_CHUNK];
415 memset(buffer, 0, sizeof(buffer));
416 int readBytes;
417
418 while ((readBytes = fread(buffer, sizeof(char), FILE_CHUNK, file)) > 0) {
419 sendData(connfd, buffer, readBytes);
420 }
421
422 fclose(file);
423
424 return true;
425}
426
427void sendHTTPHeaders(int connfd, const std::string &type, int contentLength) {
428 std::stringstream wsss;
429 wsss << "HTTP/1.1 200 OK\r\n"
430 << "Connection: keep-alive\r\n"
431 << "Content-Type: " << type << "\r\n"
432 << "Content-Length: " << contentLength << "\r\n"
433 << "\r\n";
434
435 std::string headers = wsss.str();
436 sendData(connfd, (void*)headers.c_str(), headers.size());
437}
438
439std::string getFileExtension(const std::string &filename) {
440 auto n = filename.rfind(".");
441 return filename.substr(n + 1);
442}
443
444void parseUrl(const std::string &url, std::string &resource, std::string ¶meters) {
445 // find "?"
446 auto n = url.find("?");
447 if (n != std::string::npos) {
448 resource = url.substr(0, n);
449 parameters = url.substr(n + 1);
450 } else {
451 resource = url;
452 parameters = "";
453 }
454
455 std::cout << "Resource: " << resource << " params: " << parameters << std::endl;
456}
457
458void handleResource(int connfd, const std::string &resource) {
459 std::cout << "Server resource requested: " << resource << std::endl;
460 auto it = Router.find(resource);
461
462 std::string name;
463
464 if (it != Router.end()) {
465 name = "/" + it->second;
466 } else {
467 name = resource;
468 }
469
470 std::string fullPath;
471
472 // if resource starts with served dir
473 if (resource.substr(0, ServedDirectory.size()) == ServedDirectory) {
474 fullPath = resource;
475 } else {
476 fullPath = ServedDirectory + name;
477 }
478
479 auto ext = getFileExtension(name);
480 int fileSize = getFileSize(fullPath);
481
482 std::cout << "Replying with file: " << fullPath << " of size " << fileSize << " bytes" << std::endl;
483
484 auto mimeType = getMimeType(ext);
485 sendHTTPHeaders(connfd, mimeType, fileSize);
486 sendFile(connfd, fullPath);
487}
488
489void handleAction(int connfd, const std::string &resource, const std::string ¶ms) {
490 auto it = ActionHandlers.find(resource);
491
492 if (it != ActionHandlers.end()) {
493 auto handler = it->second;
494 auto result = handler->execAction(params);
495
496 if (result->shouldRedirect()) {
497 auto redirectUrl = result->getRedirectUrl();
498 handleAction(connfd, redirectUrl, "");
499 } else {
500 std::string response = result->prepareResponse();
501 size_t size = response.size();
502 std::cout << "Replying with buffer of size " << size << " bytes" << std::endl;
503 auto mimeType = ResourceTypes["text"];
504 sendHTTPHeaders(connfd, mimeType, size);
505 void *data = (void*)response.c_str();
506 sendData(connfd, data, size);
507 }
508 } else {
509 // handle as static file
510 handleResource(connfd, resource);
511 }
512}
513
514void handleClient(int connfd) {
515 char clientmsg[MAXLEN];
516 int readSize = 0;
517
518 printf("Client says:\n");
519 memset(clientmsg, 0, sizeof(clientmsg));
520
521 std::stringstream ss;
522
523 while((readSize = recv(connfd, clientmsg, MAXLEN, 0)) > 0) {
524 printf("Received %d bytes\n", readSize);
525 ss.write(clientmsg, readSize);
526
527 bool breakExpected = false;
528
529 for (std::string message; std::getline(ss, message); ) {
530 std::cout << "Received (" << message.length() << " b): " << message << std::endl;
531
532 if (isEndOfStream(message)) {
533 breakExpected = true;
534 break;
535 }
536
537 std::stringstream line;
538 line.str(message);
539
540 std::string firstPart;
541 line >> firstPart;
542
543 if (firstPart == "GET") {
544 std::string url, resource, params;
545 line >> url;
546
547 parseUrl(url, resource, params);
548 handleAction(connfd, resource, params);
549 }
550
551/*else if (firstPart == "POST") {
552// TODO: parse post and handle POST method
553}*/
554 }
555
556 if (breakExpected) { break; }
557 }
558
559 printf("Received everything from client\n");
560}
561
562std::string getExeDirectory() {
563 char szTmp[32];
564 char pBuf[1000];
565 int len = 1000;
566 sprintf(szTmp, "/proc/%d/exe", getpid());
567 int bytes = MIN(readlink(szTmp, pBuf, len), len - 1);
568 if (bytes >= 0)
569 pBuf[bytes] = '\0';
570 std::string path(pBuf);
571
572 path.erase(path.rfind("/"));
573
574 return path;
575}
576
577void initServedDirectory(int argc, char *argv[]) {
578 if (argc == 1) {
579 ServedDirectory = getExeDirectory();
580 } else {
581 std::string pathToServe = argv[1];
582 if (pathToServe == ".") {
583 ServedDirectory = getExeDirectory();
584 } else {
585 ServedDirectory = pathToServe;
586 }
587 }
588
589 if (ServedDirectory[ServedDirectory.size() - 1] == '/') {
590 ServedDirectory.erase(ServedDirectory.size() - 1);
591 }
592
593 std::cout << "Serving " << ServedDirectory << std::endl;
594}
595
596void checkError(int code, const std::string &component) {
597 if (code < 0) {
598 std::cout << component << " failed" << std::endl;
599 exit(-1);
600 } else {
601 std::cout << component << " succeeded" << std::endl;
602 }
603}
604
605int main(int argc, char *argv[]) {
606 initServedDirectory(argc, argv);
607
608 int socketfd, connfd;
609 struct sockaddr_in server;
610 bzero((char*)&server, sizeof(server));
611
612 socketfd = socket(AF_INET, SOCK_STREAM, 0);
613 checkError(socketfd, "Create socket");
614
615 server.sin_family = AF_INET;
616 server.sin_addr.s_addr = INADDR_ANY;
617 server.sin_port = htons(PORT);
618
619 int bindResult = bind(socketfd, (struct sockaddr *)&server, sizeof(server));
620 checkError(bindResult, "Bind");
621
622 int listenResult = listen(socketfd, 3);
623 checkError(listenResult, "Listen");
624
625 initRouter();
626 initTypes();
627
628 dbManager.initialize();
629
630 initActionHandlers();
631
632 struct sockaddr_in client;
633 int clientLength;
634 bzero((char*)&client, sizeof(client));
635
636 while (1) {
637 std::cout << "Waiting for connections..." << std::endl;
638 clientLength = sizeof(client);
639 connfd = accept(socketfd, (struct sockaddr *)&client, (socklen_t*)&clientLength);
640 if (connfd < 0) {
641 std::cout << "Connection refused" << std::endl;
642 continue;
643 } else {
644 std::cout << "Connection accepted" << std::endl;
645 }
646
647 handleClient(connfd);
648
649 close(connfd);
650 std::cout << "Handled a client." << std::endl << std::endl;
651 }
652
653 return 0;
654}