Skip to content

Commit 70250c2

Browse files
committed
add: exercise answer for chapter 6 and 7
1 parent d75f10d commit 70250c2

11 files changed

+668
-0
lines changed

exercises/6/Makefile

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#
2+
# Makefile
3+
# web_server
4+
#
5+
# created by changkun at labex.io
6+
#
7+
8+
CXX = g++
9+
EXEC_HTTP = server.http
10+
EXEC_HTTPS = server.https
11+
12+
SOURCE_HTTP = main.http.cpp
13+
SOURCE_HTTPS = main.https.cpp
14+
15+
OBJECTS_HTTP = main.http.o
16+
OBJECTS_HTTPS = main.https.o
17+
18+
LDFLAGS_COMMON = -std=c++11 -O3 -pthread -lboost_system
19+
LDFLAGS_HTTP =
20+
LDFLAGS_HTTPS = -lssl -lcrypto
21+
22+
LPATH_COMMON = -I/usr/include/boost
23+
LPATH_HTTP =
24+
LPATH_HTTPS = -I/usr/include/openssl
25+
26+
LLIB_COMMON = -L/usr/lib
27+
28+
all:
29+
make http
30+
make https
31+
32+
http:
33+
$(CXX) $(SOURCE_HTTP) $(LDFLAGS_COMMON) $(LDFLAGS_HTTP) $(LPATH_COMMON) $(LPATH_HTTP) $(LLIB_COMMON) $(LLIB_HTTP) -o $(EXEC_HTTP)
34+
https:
35+
$(CXX) $(SOURCE_HTTPS) $(LDFLAGS_COMMON) $(LDFLAGS_HTTPS) $(LPATH_COMMON) $(LPATH_HTTPS) $(LLIB_COMMON) $(LLIB_HTTPS) -o $(EXEC_HTTPS)
36+
37+
clean:
38+
rm -f $(EXEC_HTTP) $(EXEC_HTTPS) *.o

exercises/6/handler.hpp

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//
2+
// handler.hpp
3+
// web_server
4+
// created by changkun at changkun.de/modern-cpp
5+
//
6+
7+
#include "server.base.hpp"
8+
#include <fstream>
9+
10+
using namespace std;
11+
using namespace LabexWeb;
12+
13+
template<typename SERVER_TYPE>
14+
void start_server(SERVER_TYPE &server) {
15+
// resources request
16+
17+
// processing POST /string, return the string from POST
18+
server.resource["^/string/?$"]["POST"] = [](ostream& response, Request& request) {
19+
// fetch string from istream (*request.content)
20+
stringstream ss;
21+
*request.content >> ss.rdbuf(); // read request to stringstream
22+
string content=ss.str();
23+
24+
// return response
25+
response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
26+
};
27+
28+
// process GET request from /info, return response
29+
server.resource["^/info/?$"]["GET"] = [](ostream& response, Request& request) {
30+
stringstream content_stream;
31+
content_stream << "<h1>Request:</h1>";
32+
content_stream << request.method << " " << request.path << " HTTP/" << request.http_version << "<br>";
33+
for(auto& header: request.header) {
34+
content_stream << header.first << ": " << header.second << "<br>";
35+
}
36+
37+
// get the length of content_stream (use content.tellp() to get)
38+
content_stream.seekp(0, ios::end);
39+
40+
response << "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n" << content_stream.rdbuf();
41+
};
42+
43+
// process GET request for /match/[digit+numbers], e.g. GET request is /match/abc123, will return abc123
44+
server.resource["^/match/([0-9a-zA-Z]+)/?$"]["GET"] = [](ostream& response, Request& request) {
45+
string number=request.path_match[1];
46+
response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number;
47+
};
48+
49+
// peocess default GET request; anonymous function will be called if no other matches
50+
// response files in folder web/
51+
// default: index.html
52+
server.default_resource["^/?(.*)$"]["GET"] = [](ostream& response, Request& request) {
53+
string filename = "www/";
54+
55+
string path = request.path_match[1];
56+
57+
// forbidden use `..` access content outside folder web/
58+
size_t last_pos = path.rfind(".");
59+
size_t current_pos = 0;
60+
size_t pos;
61+
while((pos=path.find('.', current_pos)) != string::npos && pos != last_pos) {
62+
current_pos = pos;
63+
path.erase(pos, 1);
64+
last_pos--;
65+
}
66+
67+
filename += path;
68+
ifstream ifs;
69+
// folder inspection across platform
70+
if(filename.find('.') == string::npos) {
71+
if(filename[filename.length()-1]!='/')
72+
filename+='/';
73+
filename += "index.html";
74+
}
75+
ifs.open(filename, ifstream::in);
76+
77+
if(ifs) {
78+
ifs.seekg(0, ios::end);
79+
size_t length=ifs.tellg();
80+
81+
ifs.seekg(0, ios::beg);
82+
83+
// copy file to response-stream , shouldn't use for large files
84+
response << "HTTP/1.1 200 OK\r\nContent-Length: " << length << "\r\n\r\n" << ifs.rdbuf();
85+
86+
ifs.close();
87+
} else {
88+
// return unable to open if file doesn't exists
89+
string content="Could not open file "+filename;
90+
response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
91+
}
92+
};
93+
94+
// start HTTP(S) server
95+
server.start();
96+
}

exercises/6/main.http.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// main_http.cpp
3+
// web_server
4+
// created by changkun at changkun.de/modern-cpp
5+
//
6+
7+
#include <iostream>
8+
#include "server.http.hpp"
9+
#include "handler.hpp"
10+
11+
using namespace LabexWeb;
12+
13+
int main() {
14+
// HTTP server runs in port 12345 HTTP, enable 4 threads
15+
Server<HTTP> server(12345, 4);
16+
std::cout << "Server starting at port: 12345" << std::endl;
17+
start_server<Server<HTTP>>(server);
18+
return 0;
19+
}

exercises/6/main.https.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// main_https.cpp
3+
// web_server
4+
// created by changkun at changkun.de/modern-cpp
5+
//
6+
#include <iostream>
7+
#include "server.https.hpp"
8+
#include "handler.hpp"
9+
using namespace LabexWeb;
10+
11+
int main() {
12+
// HTTPS server runs in port 12345, enable 4 threads
13+
// Use certificates for security
14+
Server<HTTPS> server(12345, 4, "server.crt", "server.key");
15+
std::cout << "Server starting at port: 12345" << std::endl;
16+
start_server<Server<HTTPS>>(server);
17+
return 0;
18+
}

exercises/6/server.base.hpp

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
//
2+
// server_base.hpp
3+
// web_server
4+
// created by changkun at changkun.de/modern-cpp
5+
//
6+
7+
#ifndef SERVER_BASE_HPP
8+
#define SERVER_BASE_HPP
9+
10+
#include <boost/asio.hpp>
11+
12+
#include <regex>
13+
#include <unordered_map>
14+
#include <thread>
15+
16+
namespace LabexWeb {
17+
struct Request {
18+
// request method, POST, GET; path; HTTP version
19+
std::string method, path, http_version;
20+
// use smart pointer for reference counting of content
21+
std::shared_ptr<std::istream> content;
22+
// hash container, key-value dict
23+
std::unordered_map<std::string, std::string> header;
24+
// use regular expression for path match
25+
std::smatch path_match;
26+
};
27+
28+
// use typedef simplify resource type
29+
typedef std::map<std::string, std::unordered_map<std::string,
30+
std::function<void(std::ostream&, Request&)>>> resource_type;
31+
32+
// socket_type is HTTP or HTTPS
33+
template <typename socket_type>
34+
class ServerBase {
35+
public:
36+
resource_type resource;
37+
resource_type default_resource;
38+
39+
// construct server, initalize port, default: 1 thread
40+
ServerBase(unsigned short port, size_t num_threads = 1) :
41+
endpoint(boost::asio::ip::tcp::v4(), port),
42+
acceptor(m_io_service, endpoint),
43+
num_threads(num_threads) {}
44+
45+
void start() {
46+
// default resource in the end of vector, as response method
47+
for(auto it = resource.begin(); it != resource.end(); it++) {
48+
all_resources.push_back(it);
49+
}
50+
for(auto it = default_resource.begin(); it != default_resource.end(); it++) {
51+
all_resources.push_back(it);
52+
}
53+
54+
// socket connection
55+
accept();
56+
57+
// if num_threads>1, then m_io_service.run()
58+
// it will start (num_threads-1) threads as thread pool
59+
for(size_t c = 1;c < num_threads; c++) {
60+
threads.emplace_back([this](){
61+
m_io_service.run();
62+
});
63+
}
64+
65+
// main thread
66+
m_io_service.run();
67+
68+
// wait for other threads finish
69+
for(auto& t: threads)
70+
t.join();
71+
}
72+
protected:
73+
// io_service is a dispatcher in asio library, all asynchronous io events are dispatched by it
74+
// in another word, constructor of IO object need a io_service object as parameter
75+
boost::asio::io_service m_io_service;
76+
// IP address, port and protocol version as a endpoint, and generated on serverside
77+
// tcp::acceptor object, wait for connection
78+
boost::asio::ip::tcp::endpoint endpoint;
79+
// thus, a acceptor object requires io_service and endpoint as parameters
80+
boost::asio::ip::tcp::acceptor acceptor;
81+
82+
// server side threads
83+
size_t num_threads;
84+
std::vector<std::thread> threads;
85+
86+
// all resource will be append to the end of vector, and created in start()
87+
std::vector<resource_type::iterator> all_resources;
88+
89+
// requires to implement this method for different type of server
90+
virtual void accept() {}
91+
92+
void process_request_and_respond(std::shared_ptr<socket_type> socket) const {
93+
// created cache for async_read_untile()
94+
// shared_ptr will use for passing object to anonymous function
95+
// the type will be deduce as std::shared_ptr<boost::asio::streambuf>
96+
auto read_buffer = std::make_shared<boost::asio::streambuf>();
97+
98+
boost::asio::async_read_until(*socket, *read_buffer, "\r\n\r\n",
99+
[this, socket, read_buffer](const boost::system::error_code& ec, size_t bytes_transferred) {
100+
if(!ec) {
101+
// Note: read_buffer->size() always equal to bytes_transferred, the document of Boost indicates:
102+
// after async_read_until operation, streambuf contains some extra data out of delimiter
103+
// thus, the best way is to read and parse the content from the left of read_buffer, then append the content of async_read
104+
size_t total = read_buffer->size();
105+
106+
// convert istream to string-lines
107+
std::istream stream(read_buffer.get());
108+
109+
// deduce the type of std::shared_ptr<Request>
110+
auto request = std::make_shared<Request>();
111+
*request = parse_request(stream);
112+
113+
size_t num_additional_bytes = total-bytes_transferred;
114+
115+
// if satisfy then also read
116+
if(request->header.count("Content-Length")>0) {
117+
boost::asio::async_read(*socket, *read_buffer,
118+
boost::asio::transfer_exactly(stoull(request->header["Content-Length"]) - num_additional_bytes),
119+
[this, socket, read_buffer, request](const boost::system::error_code& ec, size_t bytes_transferred) {
120+
if(!ec) {
121+
// pointer as istream object stored in read_buffer
122+
request->content = std::shared_ptr<std::istream>(new std::istream(read_buffer.get()));
123+
respond(socket, request);
124+
}
125+
});
126+
} else {
127+
respond(socket, request);
128+
}
129+
}
130+
});
131+
}
132+
133+
Request parse_request(std::istream& stream) const {
134+
Request request;
135+
136+
std::regex e("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$");
137+
138+
std::smatch sub_match;
139+
140+
// read method, path and http version from the frist line
141+
std::string line;
142+
getline(stream, line);
143+
line.pop_back();
144+
if(std::regex_match(line, sub_match, e)) {
145+
request.method = sub_match[1];
146+
request.path = sub_match[2];
147+
request.http_version = sub_match[3];
148+
149+
bool matched;
150+
e="^([^:]*): ?(.*)$";
151+
// parse head information
152+
do {
153+
getline(stream, line);
154+
line.pop_back();
155+
matched=std::regex_match(line, sub_match, e);
156+
if(matched) {
157+
request.header[sub_match[1]] = sub_match[2];
158+
}
159+
160+
} while(matched==true);
161+
}
162+
return request;
163+
}
164+
165+
void respond(std::shared_ptr<socket_type> socket, std::shared_ptr<Request> request) const {
166+
// response after search requested path and method
167+
for(auto res_it: all_resources) {
168+
std::regex e(res_it->first);
169+
std::smatch sm_res;
170+
if(std::regex_match(request->path, sm_res, e)) {
171+
if(res_it->second.count(request->method)>0) {
172+
request->path_match = move(sm_res);
173+
174+
// will be deduce to std::shared_ptr<boost::asio::streambuf>
175+
auto write_buffer = std::make_shared<boost::asio::streambuf>();
176+
std::ostream response(write_buffer.get());
177+
res_it->second[request->method](response, *request);
178+
179+
// capture write_buffer in lambda, make sure it can be destroyed after async_write
180+
boost::asio::async_write(*socket, *write_buffer,
181+
[this, socket, request, write_buffer](const boost::system::error_code& ec, size_t bytes_transferred) {
182+
// HTTP 1.1 connection
183+
if(!ec && stof(request->http_version)>1.05)
184+
process_request_and_respond(socket);
185+
});
186+
return;
187+
}
188+
}
189+
}
190+
}
191+
192+
};
193+
194+
template<typename socket_type>
195+
class Server : public ServerBase<socket_type> {};
196+
}
197+
#endif /* SERVER_BASE_HPP */

0 commit comments

Comments
 (0)