1//
2// Copyright (c) 2017 Christopher M. Kohlhoff (chris at kohlhoff dot com)
3//
4// Distributed under the Boost Software License, Version 1.0. (See accompanying
5// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6//
7// Official repository: https://github.com/boostorg/beast
8//
9
10//------------------------------------------------------------------------------
11//
12// Example: HTTP server, fast
13//
14//------------------------------------------------------------------------------
15
16#include "fields_alloc.hpp"
17
18#include <boost/beast/core.hpp>
19#include <boost/beast/http.hpp>
20#include <boost/beast/version.hpp>
21#include <boost/asio.hpp>
22#include <chrono>
23#include <cstdlib>
24#include <cstring>
25#include <iostream>
26#include <list>
27#include <memory>
28#include <string>
29
30namespace beast = boost::beast; // from <boost/beast.hpp>
31namespace http = beast::http; // from <boost/beast/http.hpp>
32namespace net = boost::asio; // from <boost/asio.hpp>
33using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
34
35// Return a reasonable mime type based on the extension of a file.
36beast::string_view
37mime_type(beast::string_view path)
38{
39 using beast::iequals;
40 auto const ext = [&path]
41 {
42 auto const pos = path.rfind(s: ".");
43 if(pos == beast::string_view::npos)
44 return beast::string_view{};
45 return path.substr(pos);
46 }();
47 if(iequals(lhs: ext, rhs: ".htm")) return "text/html";
48 if(iequals(lhs: ext, rhs: ".html")) return "text/html";
49 if(iequals(lhs: ext, rhs: ".php")) return "text/html";
50 if(iequals(lhs: ext, rhs: ".css")) return "text/css";
51 if(iequals(lhs: ext, rhs: ".txt")) return "text/plain";
52 if(iequals(lhs: ext, rhs: ".js")) return "application/javascript";
53 if(iequals(lhs: ext, rhs: ".json")) return "application/json";
54 if(iequals(lhs: ext, rhs: ".xml")) return "application/xml";
55 if(iequals(lhs: ext, rhs: ".swf")) return "application/x-shockwave-flash";
56 if(iequals(lhs: ext, rhs: ".flv")) return "video/x-flv";
57 if(iequals(lhs: ext, rhs: ".png")) return "image/png";
58 if(iequals(lhs: ext, rhs: ".jpe")) return "image/jpeg";
59 if(iequals(lhs: ext, rhs: ".jpeg")) return "image/jpeg";
60 if(iequals(lhs: ext, rhs: ".jpg")) return "image/jpeg";
61 if(iequals(lhs: ext, rhs: ".gif")) return "image/gif";
62 if(iequals(lhs: ext, rhs: ".bmp")) return "image/bmp";
63 if(iequals(lhs: ext, rhs: ".ico")) return "image/vnd.microsoft.icon";
64 if(iequals(lhs: ext, rhs: ".tiff")) return "image/tiff";
65 if(iequals(lhs: ext, rhs: ".tif")) return "image/tiff";
66 if(iequals(lhs: ext, rhs: ".svg")) return "image/svg+xml";
67 if(iequals(lhs: ext, rhs: ".svgz")) return "image/svg+xml";
68 return "application/text";
69}
70
71class http_worker
72{
73public:
74 http_worker(http_worker const&) = delete;
75 http_worker& operator=(http_worker const&) = delete;
76
77 http_worker(tcp::acceptor& acceptor, const std::string& doc_root) :
78 acceptor_(acceptor),
79 doc_root_(doc_root)
80 {
81 }
82
83 void start()
84 {
85 accept();
86 check_deadline();
87 }
88
89private:
90 using alloc_t = fields_alloc<char>;
91 //using request_body_t = http::basic_dynamic_body<beast::flat_static_buffer<1024 * 1024>>;
92 using request_body_t = http::string_body;
93
94 // The acceptor used to listen for incoming connections.
95 tcp::acceptor& acceptor_;
96
97 // The path to the root of the document directory.
98 std::string doc_root_;
99
100 // The socket for the currently connected client.
101 tcp::socket socket_{acceptor_.get_executor()};
102
103 // The buffer for performing reads
104 beast::flat_static_buffer<8192> buffer_;
105
106 // The allocator used for the fields in the request and reply.
107 alloc_t alloc_{8192};
108
109 // The parser for reading the requests
110 boost::optional<http::request_parser<request_body_t, alloc_t>> parser_;
111
112 // The timer putting a time limit on requests.
113 net::steady_timer request_deadline_{
114 acceptor_.get_executor(), (std::chrono::steady_clock::time_point::max)()};
115
116 // The string-based response message.
117 boost::optional<http::response<http::string_body, http::basic_fields<alloc_t>>> string_response_;
118
119 // The string-based response serializer.
120 boost::optional<http::response_serializer<http::string_body, http::basic_fields<alloc_t>>> string_serializer_;
121
122 // The file-based response message.
123 boost::optional<http::response<http::file_body, http::basic_fields<alloc_t>>> file_response_;
124
125 // The file-based response serializer.
126 boost::optional<http::response_serializer<http::file_body, http::basic_fields<alloc_t>>> file_serializer_;
127
128 void accept()
129 {
130 // Clean up any previous connection.
131 beast::error_code ec;
132 socket_.close(ec);
133 buffer_.consume(n: buffer_.size());
134
135 acceptor_.async_accept(
136 peer&: socket_,
137 token: [this](beast::error_code ec)
138 {
139 if (ec)
140 {
141 accept();
142 }
143 else
144 {
145 // Request must be fully processed within 60 seconds.
146 request_deadline_.expires_after(
147 expiry_time: std::chrono::seconds(60));
148
149 read_request();
150 }
151 });
152 }
153
154 void read_request()
155 {
156 // On each read the parser needs to be destroyed and
157 // recreated. We store it in a boost::optional to
158 // achieve that.
159 //
160 // Arguments passed to the parser constructor are
161 // forwarded to the message object. A single argument
162 // is forwarded to the body constructor.
163 //
164 // We construct the dynamic body with a 1MB limit
165 // to prevent vulnerability to buffer attacks.
166 //
167 parser_.emplace(
168 args: std::piecewise_construct,
169 args: std::make_tuple(),
170 args: std::make_tuple(args&: alloc_));
171
172 http::async_read(
173 stream&: socket_,
174 buffer&: buffer_,
175 parser&: *parser_,
176 handler: [this](beast::error_code ec, std::size_t)
177 {
178 if (ec)
179 accept();
180 else
181 process_request(req: parser_->get());
182 });
183 }
184
185 void process_request(http::request<request_body_t, http::basic_fields<alloc_t>> const& req)
186 {
187 switch (req.method())
188 {
189 case http::verb::get:
190 send_file(target: req.target());
191 break;
192
193 default:
194 // We return responses indicating an error if
195 // we do not recognize the request method.
196 send_bad_response(
197 status: http::status::bad_request,
198 error: "Invalid request-method '" + std::string(req.method_string()) + "'\r\n");
199 break;
200 }
201 }
202
203 void send_bad_response(
204 http::status status,
205 std::string const& error)
206 {
207 string_response_.emplace(
208 args: std::piecewise_construct,
209 args: std::make_tuple(),
210 args: std::make_tuple(args&: alloc_));
211
212 string_response_->result(v: status);
213 string_response_->keep_alive(value: false);
214 string_response_->set(name: http::field::server, value: "Beast");
215 string_response_->set(name: http::field::content_type, value: "text/plain");
216 string_response_->body() = error;
217 string_response_->prepare_payload();
218
219 string_serializer_.emplace(args&: *string_response_);
220
221 http::async_write(
222 stream&: socket_,
223 sr&: *string_serializer_,
224 handler: [this](beast::error_code ec, std::size_t)
225 {
226 socket_.shutdown(what: tcp::socket::shutdown_send, ec);
227 string_serializer_.reset();
228 string_response_.reset();
229 accept();
230 });
231 }
232
233 void send_file(beast::string_view target)
234 {
235 // Request path must be absolute and not contain "..".
236 if (target.empty() || target[0] != '/' || target.find(s: "..") != std::string::npos)
237 {
238 send_bad_response(
239 status: http::status::not_found,
240 error: "File not found\r\n");
241 return;
242 }
243
244 std::string full_path = doc_root_;
245 full_path.append(
246 s: target.data(),
247 n: target.size());
248
249 http::file_body::value_type file;
250 beast::error_code ec;
251 file.open(
252 path: full_path.c_str(),
253 mode: beast::file_mode::read,
254 ec);
255 if(ec)
256 {
257 send_bad_response(
258 status: http::status::not_found,
259 error: "File not found\r\n");
260 return;
261 }
262
263 file_response_.emplace(
264 args: std::piecewise_construct,
265 args: std::make_tuple(),
266 args: std::make_tuple(args&: alloc_));
267
268 file_response_->result(v: http::status::ok);
269 file_response_->keep_alive(value: false);
270 file_response_->set(name: http::field::server, value: "Beast");
271 file_response_->set(name: http::field::content_type, value: mime_type(path: std::string(target)));
272 file_response_->body() = std::move(file);
273 file_response_->prepare_payload();
274
275 file_serializer_.emplace(args&: *file_response_);
276
277 http::async_write(
278 stream&: socket_,
279 sr&: *file_serializer_,
280 handler: [this](beast::error_code ec, std::size_t)
281 {
282 socket_.shutdown(what: tcp::socket::shutdown_send, ec);
283 file_serializer_.reset();
284 file_response_.reset();
285 accept();
286 });
287 }
288
289 void check_deadline()
290 {
291 // The deadline may have moved, so check it has really passed.
292 if (request_deadline_.expiry() <= std::chrono::steady_clock::now())
293 {
294 // Close socket to cancel any outstanding operation.
295 socket_.close();
296
297 // Sleep indefinitely until we're given a new deadline.
298 request_deadline_.expires_at(
299 expiry_time: (std::chrono::steady_clock::time_point::max)());
300 }
301
302 request_deadline_.async_wait(
303 token: [this](beast::error_code)
304 {
305 check_deadline();
306 });
307 }
308};
309
310int main(int argc, char* argv[])
311{
312 try
313 {
314 // Check command line arguments.
315 if (argc != 6)
316 {
317 std::cerr << "Usage: http_server_fast <address> <port> <doc_root> <num_workers> {spin|block}\n";
318 std::cerr << " For IPv4, try:\n";
319 std::cerr << " http_server_fast 0.0.0.0 80 . 100 block\n";
320 std::cerr << " For IPv6, try:\n";
321 std::cerr << " http_server_fast 0::0 80 . 100 block\n";
322 return EXIT_FAILURE;
323 }
324
325 auto const address = net::ip::make_address(str: argv[1]);
326 unsigned short port = static_cast<unsigned short>(std::atoi(nptr: argv[2]));
327 std::string doc_root = argv[3];
328 int num_workers = std::atoi(nptr: argv[4]);
329 bool spin = (std::strcmp(s1: argv[5], s2: "spin") == 0);
330
331 net::io_context ioc{1};
332 tcp::acceptor acceptor{ioc, {address, port}};
333
334 std::list<http_worker> workers;
335 for (int i = 0; i < num_workers; ++i)
336 {
337 workers.emplace_back(args&: acceptor, args&: doc_root);
338 workers.back().start();
339 }
340
341 if (spin)
342 for (;;) ioc.poll();
343 else
344 ioc.run();
345 }
346 catch (const std::exception& e)
347 {
348 std::cerr << "Error: " << e.what() << std::endl;
349 return EXIT_FAILURE;
350 }
351}
352

source code of boost/libs/beast/example/http/server/fast/http_server_fast.cpp