forked from YukunJ/Turtle
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhttp_server.cpp
More file actions
144 lines (140 loc) · 5.1 KB
/
http_server.cpp
File metadata and controls
144 lines (140 loc) · 5.1 KB
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
/**
* @file http_server.cpp
* @author Yukun J
* @expectation this is the http server for illustration and test purpose
* @init_date Jan 3 2023
*/
#include "core/turtle_server.h"
#include "http/cgier.h"
#include "http/header.h"
#include "http/http_utils.h"
#include "http/request.h"
#include "http/response.h"
#include "log/logger.h"
namespace TURTLE_SERVER::HTTP {
void ProcessHttpRequest( // NOLINT
const std::string &serving_directory,
std::shared_ptr<Cache> &cache, // NOLINT
Connection *client_conn) {
// edge-trigger, first read all available bytes
int from_fd = client_conn->GetFd();
auto [read, exit] = client_conn->Recv();
if (exit) {
client_conn->GetLooper()->DeleteConnection(from_fd);
LOG_INFO("client fd=" + std::to_string(from_fd) + " has exited");
// client_conn ptr is invalid below here, do not touch it again
return;
}
// check if there is any complete http request ready
bool no_more_parse = false;
std::optional<std::string> request_op = client_conn->FindAndPopTill("\r\n\r\n");
while (request_op != std::nullopt) {
Request request{request_op.value()};
std::vector<unsigned char> response_buf;
if (!request.IsValid()) {
auto response = Response::Make400Response();
no_more_parse = true;
response.Serialize(response_buf);
} else {
std::string resource_full_path = serving_directory + request.GetResourceUrl();
if (IsCgiRequest(resource_full_path)) {
// dynamic CGI request
Cgier cgier = Cgier::ParseCgier(resource_full_path);
if (!cgier.IsValid()) {
auto response = Response::Make400Response();
no_more_parse = true;
response.Serialize(response_buf);
} else {
auto cgi_program_path = cgier.GetPath();
if (!IsFileExists(cgi_program_path)) {
auto response = Response::Make404Response();
no_more_parse = true;
response.Serialize(response_buf);
} else {
auto cgi_result = cgier.Run();
auto response = Response::Make200Response(request.ShouldClose(), std::nullopt);
response.ChangeHeader(HEADER_CONTENT_LENGTH, std::to_string(cgi_result.size()));
no_more_parse = request.ShouldClose();
response.Serialize(response_buf);
response_buf.insert(response_buf.end(), cgi_result.begin(), cgi_result.end());
}
}
} else {
// static resource request
if (!IsFileExists(resource_full_path)) {
auto response = Response::Make404Response();
no_more_parse = true;
response.Serialize(response_buf);
} else {
auto response = Response::Make200Response(request.ShouldClose(), resource_full_path);
response.Serialize(response_buf);
no_more_parse = request.ShouldClose();
std::vector<unsigned char> cache_buf;
if (request.GetMethod() == Method::GET) {
// only concern about carrying content when GET request
bool resource_cached = cache->TryLoad(resource_full_path, cache_buf);
if (!resource_cached) {
// if content directly from cache, not disk file I/O
// otherwise content not in cache, load from disk and try cache it
LoadFile(resource_full_path, cache_buf);
cache->TryInsert(resource_full_path, cache_buf);
}
}
// now cache_buf contains the file content anyway
response_buf.insert(response_buf.end(), cache_buf.begin(), cache_buf.end());
}
}
}
// send out the response
client_conn->WriteToWriteBuffer(std::move(response_buf));
client_conn->Send();
if (no_more_parse) {
break;
}
request_op = client_conn->FindAndPopTill("\r\n\r\n");
}
if (no_more_parse) {
client_conn->GetLooper()->DeleteConnection(from_fd);
// client_conn ptr is invalid below here, do not touch it again
return;
}
}
} // namespace TURTLE_SERVER::HTTP
int main(int argc, char *argv[]) {
const std::string usage =
"Usage: \n"
"./http_server [optional: port default=20080] [optional: directory "
"default=../http_dir/] \n";
if (argc > 3) {
std::cout << "argument number error\n";
std::cout << usage;
exit(EXIT_FAILURE);
}
TURTLE_SERVER::NetAddress address("0.0.0.0", 20080);
std::string directory = "../http_dir/";
if (argc >= 2) {
auto port = static_cast<uint16_t>(std::strtol(argv[1], nullptr, 10));
if (port == 0) {
std::cout << "port error\n";
std::cout << usage;
exit(EXIT_FAILURE);
}
address = {"0.0.0.0", port};
if (argc == 3) {
directory = argv[2];
if (!TURTLE_SERVER::HTTP::IsDirectoryExists(directory)) {
std::cout << "directory error\n";
std::cout << usage;
exit(EXIT_FAILURE);
}
}
}
TURTLE_SERVER::TurtleServer http_server(address);
auto cache = std::make_shared<TURTLE_SERVER::Cache>();
http_server
.OnHandle([&](TURTLE_SERVER::Connection *client_conn) {
TURTLE_SERVER::HTTP::ProcessHttpRequest(directory, cache, client_conn);
})
.Begin();
return 0;
}