-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathwebserver_https.cc
More file actions
204 lines (173 loc) · 7.97 KB
/
webserver_https.cc
File metadata and controls
204 lines (173 loc) · 7.97 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
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
#include "webserver_https.h"
#include <cstring>
WebServerHTTPS::WebServerHTTPS(NginxConfig config, unsigned short port, size_t num_threads,
const string& cert_file, const string& private_key_file)
: endpoint(ip::tcp::v4(), port), acceptor(m_io_service, endpoint), num_threads(num_threads), context(ssl::context::sslv23)
{
extract(config);
context.use_certificate_chain_file(cert_file);
context.use_private_key_file(private_key_file, ssl::context::pem);
}
void WebServerHTTPS::run() {
do_accept();
//If num_threads>1, start m_io_service.run() in (num_threads-1) threads for thread-pooling
for(size_t c=1;c<num_threads;c++) {
threads.emplace_back([this](){ m_io_service.run();});
}
//Main thread
m_io_service.run();
//Wait for the rest of the threads, if any, to finish as well
for(thread& t: threads) { t.join(); }
}
void WebServerHTTPS::do_accept() {
//Create new socket for this connection
//Shared_ptr is used to pass temporary objects to the asynchronous functions
shared_ptr<ssl::stream<ip::tcp::socket>> socket(new ssl::stream<ip::tcp::socket>(m_io_service, context));
acceptor.async_accept((*socket).lowest_layer(), [this, socket](const boost::system::error_code& ec) {
//Immediately start accepting a new connection
do_accept();
if(!ec) {
(*socket).async_handshake(ssl::stream_base::server, [this, socket](const boost::system::error_code& ec) {
if(!ec) {
process_request(socket);
}
});
}
});
}
void WebServerHTTPS::process_request(shared_ptr<ssl::stream<ip::tcp::socket>> socket) {
//Create new read_buffer for async_read_until()
//Shared_ptr is used to pass temporary objects to the asynchronous functions
shared_ptr<boost::asio::streambuf> read_buffer(new boost::asio::streambuf);
async_read_until(*socket, *read_buffer, "\r\n\r\n",
[this, socket, read_buffer](const boost::system::error_code& ec, size_t bytes_transferred) {
if(!ec) {
//read_buffer->size() is not necessarily the same as bytes_transferred, from Boost-docs:
//"After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter"
//The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the
//read_buffer (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).
size_t total=read_buffer->size();
size_t num_additional_bytes=total-bytes_transferred;
string raw_request((istreambuf_iterator<char>(read_buffer.get())), istreambuf_iterator<char>());
unique_ptr<Request> request = Request::Parse(raw_request);
auto headers = request->headers();
//If content, read that as well
auto it = find_if(headers.begin(), headers.end(), [](const std::pair<string, string>& header) {
return header.first == "Content-Length";
} );
if(it != headers.end() && it->second != "0") {
cout<<"content length is "<<it->second<<endl;
async_read(*socket, *read_buffer, transfer_exactly(stoull(it->second)-num_additional_bytes),
[this, socket, read_buffer, &request](const boost::system::error_code& ec, size_t bytes_transferred) {
if(!ec) {
string body((istreambuf_iterator<char>(read_buffer.get())), istreambuf_iterator<char>());
cout<<"body is "<<body<<endl;
request->setBody(body);
do_reply(socket, request);
}
});
}
else { do_reply(socket, request); }
}
});
}
void WebServerHTTPS::do_reply(shared_ptr<ssl::stream<ip::tcp::socket>> socket, const unique_ptr<Request> &request) {
shared_ptr<boost::asio::streambuf> write_buffer(new boost::asio::streambuf);
ostream response(write_buffer.get());
// Use longest prefix matching to map to corresponding request handler
string uri = request->uri();
size_t pos;
shared_ptr<RequestHandler> handler = nullptr;
string prefix;
while ((pos = uri.find_last_of("/")) != string::npos) {
if(pos == 0){
prefix = uri.substr(0, pos+1);
}
else{
prefix = uri.substr(0, pos);
}
auto it = prefix2handler.find(prefix);
if (it != prefix2handler.end()) {
handler = prefix2handler[prefix];
break;
}
uri = prefix;
}
Response res;
// Response_code (200, 404, etc) will always appear after "HTTP/1.1 ".
// So get the const size of http version and the const length of response_code.
// Then, response_code can be obtained from substr of response.ToString() function
// and be written to singleton Log class.
const int http_version_size = strlen("HTTP/1.1 ");
const int response_code_len = 3;
if (handler) {
handler->HandleRequest(*request, &res);
// Write status information of the server to singleton Log class.
Log::instance()->set_status(request->uri(), res.ToString().substr(http_version_size, response_code_len), prefix2handler_type[prefix], prefix);
}
else{
prefix2handler["default"]->HandleRequest(*request, &res);
// Write status information of the server to singleton Log class.
Log::instance()->set_status(request->uri(), res.ToString().substr(http_version_size, response_code_len), "NotFoundHandler", "");
}
response << res.ToString();
int version = stoi(request->version());
// Capture write_buffer in lambda so it is not destroyed before async_write is finished
async_write(*socket, *write_buffer, [this, socket, write_buffer, version](const boost::system::error_code& ec, size_t bytes_transferred) {
//HTTP persistent connection (HTTP 1.1):
if(!ec && version>=1.1) {
process_request(socket);
}
});
return;
}
void extract_port(NginxConfig config, unsigned short &port) {
// Initialize variables
string key = "";
string value = "";
for (size_t i = 0; i < config.statements_.size(); i++) {
//search in child block
if (config.statements_[i]->child_block_ != nullptr) {
extract_port(*(config.statements_[i]->child_block_), port);
}
if (config.statements_[i]->tokens_.size() >= 1) {
key = config.statements_[i]->tokens_[0];
}
if (config.statements_[i]->tokens_.size() >= 2) {
value = config.statements_[i]->tokens_[1];
}
if (key == "port" && value != "") {
port = atoi(value.c_str());
}
}
}
void WebServerHTTPS::extract(NginxConfig config) {
// Initialize variables
string key = "";
string value = "";
for (size_t i = 0; i < config.statements_.size(); i++) {
//search in child block
if (config.statements_[i]->child_block_ != nullptr) {
// create corresponding request handler objects
if(config.statements_[i]->tokens_[0] == "path"){
key = config.statements_[i]->tokens_[1];
string handler_type = config.statements_[i]->tokens_[2];
// create a request handler pointer by handler_type
auto handler = RequestHandler::CreateByName(handler_type.c_str());
// and then assign it to the corresponding shared pointer in prefix2handler map
prefix2handler[key].reset(handler);
prefix2handler[key]->Init(key, *(config.statements_[i]->child_block_));
// record handler type for logging status of the server
prefix2handler_type[key] = handler_type;
}
else if(config.statements_[i]->tokens_[0] == "default"){
string handler_type = config.statements_[i]->tokens_[1];
auto handler = RequestHandler::CreateByName(handler_type.c_str());
prefix2handler["default"].reset(handler);
}
else{
extract(*(config.statements_[i]->child_block_));
}
}
}
}