• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
30 namespace beast = boost::beast;         // from <boost/beast.hpp>
31 namespace http = beast::http;           // from <boost/beast/http.hpp>
32 namespace net = boost::asio;            // from <boost/asio.hpp>
33 using 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.
36 beast::string_view
mime_type(beast::string_view path)37 mime_type(beast::string_view path)
38 {
39     using beast::iequals;
40     auto const ext = [&path]
41     {
42         auto const pos = path.rfind(".");
43         if(pos == beast::string_view::npos)
44             return beast::string_view{};
45         return path.substr(pos);
46     }();
47     if(iequals(ext, ".htm"))  return "text/html";
48     if(iequals(ext, ".html")) return "text/html";
49     if(iequals(ext, ".php"))  return "text/html";
50     if(iequals(ext, ".css"))  return "text/css";
51     if(iequals(ext, ".txt"))  return "text/plain";
52     if(iequals(ext, ".js"))   return "application/javascript";
53     if(iequals(ext, ".json")) return "application/json";
54     if(iequals(ext, ".xml"))  return "application/xml";
55     if(iequals(ext, ".swf"))  return "application/x-shockwave-flash";
56     if(iequals(ext, ".flv"))  return "video/x-flv";
57     if(iequals(ext, ".png"))  return "image/png";
58     if(iequals(ext, ".jpe"))  return "image/jpeg";
59     if(iequals(ext, ".jpeg")) return "image/jpeg";
60     if(iequals(ext, ".jpg"))  return "image/jpeg";
61     if(iequals(ext, ".gif"))  return "image/gif";
62     if(iequals(ext, ".bmp"))  return "image/bmp";
63     if(iequals(ext, ".ico"))  return "image/vnd.microsoft.icon";
64     if(iequals(ext, ".tiff")) return "image/tiff";
65     if(iequals(ext, ".tif"))  return "image/tiff";
66     if(iequals(ext, ".svg"))  return "image/svg+xml";
67     if(iequals(ext, ".svgz")) return "image/svg+xml";
68     return "application/text";
69 }
70 
71 class http_worker
72 {
73 public:
74     http_worker(http_worker const&) = delete;
75     http_worker& operator=(http_worker const&) = delete;
76 
http_worker(tcp::acceptor & acceptor,const std::string & doc_root)77     http_worker(tcp::acceptor& acceptor, const std::string& doc_root) :
78         acceptor_(acceptor),
79         doc_root_(doc_root)
80     {
81     }
82 
start()83     void start()
84     {
85         accept();
86         check_deadline();
87     }
88 
89 private:
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 
accept()128     void accept()
129     {
130         // Clean up any previous connection.
131         beast::error_code ec;
132         socket_.close(ec);
133         buffer_.consume(buffer_.size());
134 
135         acceptor_.async_accept(
136             socket_,
137             [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                         std::chrono::seconds(60));
148 
149                     read_request();
150                 }
151             });
152     }
153 
read_request()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             std::piecewise_construct,
169             std::make_tuple(),
170             std::make_tuple(alloc_));
171 
172         http::async_read(
173             socket_,
174             buffer_,
175             *parser_,
176             [this](beast::error_code ec, std::size_t)
177             {
178                 if (ec)
179                     accept();
180                 else
181                     process_request(parser_->get());
182             });
183     }
184 
process_request(http::request<request_body_t,http::basic_fields<alloc_t>> const & req)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(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                 http::status::bad_request,
198                 "Invalid request-method '" + std::string(req.method_string()) + "'\r\n");
199             break;
200         }
201     }
202 
send_bad_response(http::status status,std::string const & error)203     void send_bad_response(
204         http::status status,
205         std::string const& error)
206     {
207         string_response_.emplace(
208             std::piecewise_construct,
209             std::make_tuple(),
210             std::make_tuple(alloc_));
211 
212         string_response_->result(status);
213         string_response_->keep_alive(false);
214         string_response_->set(http::field::server, "Beast");
215         string_response_->set(http::field::content_type, "text/plain");
216         string_response_->body() = error;
217         string_response_->prepare_payload();
218 
219         string_serializer_.emplace(*string_response_);
220 
221         http::async_write(
222             socket_,
223             *string_serializer_,
224             [this](beast::error_code ec, std::size_t)
225             {
226                 socket_.shutdown(tcp::socket::shutdown_send, ec);
227                 string_serializer_.reset();
228                 string_response_.reset();
229                 accept();
230             });
231     }
232 
send_file(beast::string_view target)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("..") != std::string::npos)
237         {
238             send_bad_response(
239                 http::status::not_found,
240                 "File not found\r\n");
241             return;
242         }
243 
244         std::string full_path = doc_root_;
245         full_path.append(
246             target.data(),
247             target.size());
248 
249         http::file_body::value_type file;
250         beast::error_code ec;
251         file.open(
252             full_path.c_str(),
253             beast::file_mode::read,
254             ec);
255         if(ec)
256         {
257             send_bad_response(
258                 http::status::not_found,
259                 "File not found\r\n");
260             return;
261         }
262 
263         file_response_.emplace(
264             std::piecewise_construct,
265             std::make_tuple(),
266             std::make_tuple(alloc_));
267 
268         file_response_->result(http::status::ok);
269         file_response_->keep_alive(false);
270         file_response_->set(http::field::server, "Beast");
271         file_response_->set(http::field::content_type, mime_type(std::string(target)));
272         file_response_->body() = std::move(file);
273         file_response_->prepare_payload();
274 
275         file_serializer_.emplace(*file_response_);
276 
277         http::async_write(
278             socket_,
279             *file_serializer_,
280             [this](beast::error_code ec, std::size_t)
281             {
282                 socket_.shutdown(tcp::socket::shutdown_send, ec);
283                 file_serializer_.reset();
284                 file_response_.reset();
285                 accept();
286             });
287     }
288 
check_deadline()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                 (std::chrono::steady_clock::time_point::max)());
300         }
301 
302         request_deadline_.async_wait(
303             [this](beast::error_code)
304             {
305                 check_deadline();
306             });
307     }
308 };
309 
main(int argc,char * argv[])310 int 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(argv[1]);
326         unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));
327         std::string doc_root = argv[3];
328         int num_workers = std::atoi(argv[4]);
329         bool spin = (std::strcmp(argv[5], "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(acceptor, 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