http_server_fast.cpp 11 KB

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