http_session.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. //
  2. // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail 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/vinniefalco/CppCon2018
  8. //
  9. #include "http_session.hpp"
  10. #include "websocket_session.hpp"
  11. #include <boost/config.hpp>
  12. #include <iostream>
  13. #define BOOST_NO_CXX14_GENERIC_LAMBDAS
  14. //------------------------------------------------------------------------------
  15. // Return a reasonable mime type based on the extension of a file.
  16. beast::string_view
  17. mime_type(beast::string_view path)
  18. {
  19. using beast::iequals;
  20. auto const ext = [&path]
  21. {
  22. auto const pos = path.rfind(".");
  23. if(pos == beast::string_view::npos)
  24. return beast::string_view{};
  25. return path.substr(pos);
  26. }();
  27. if(iequals(ext, ".htm")) return "text/html";
  28. if(iequals(ext, ".html")) return "text/html";
  29. if(iequals(ext, ".php")) return "text/html";
  30. if(iequals(ext, ".css")) return "text/css";
  31. if(iequals(ext, ".txt")) return "text/plain";
  32. if(iequals(ext, ".js")) return "application/javascript";
  33. if(iequals(ext, ".json")) return "application/json";
  34. if(iequals(ext, ".xml")) return "application/xml";
  35. if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
  36. if(iequals(ext, ".flv")) return "video/x-flv";
  37. if(iequals(ext, ".png")) return "image/png";
  38. if(iequals(ext, ".jpe")) return "image/jpeg";
  39. if(iequals(ext, ".jpeg")) return "image/jpeg";
  40. if(iequals(ext, ".jpg")) return "image/jpeg";
  41. if(iequals(ext, ".gif")) return "image/gif";
  42. if(iequals(ext, ".bmp")) return "image/bmp";
  43. if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
  44. if(iequals(ext, ".tiff")) return "image/tiff";
  45. if(iequals(ext, ".tif")) return "image/tiff";
  46. if(iequals(ext, ".svg")) return "image/svg+xml";
  47. if(iequals(ext, ".svgz")) return "image/svg+xml";
  48. return "application/text";
  49. }
  50. // Append an HTTP rel-path to a local filesystem path.
  51. // The returned path is normalized for the platform.
  52. std::string
  53. path_cat(
  54. beast::string_view base,
  55. beast::string_view path)
  56. {
  57. if(base.empty())
  58. return std::string(path);
  59. std::string result(base);
  60. #ifdef BOOST_MSVC
  61. char constexpr path_separator = '\\';
  62. if(result.back() == path_separator)
  63. result.resize(result.size() - 1);
  64. result.append(path.data(), path.size());
  65. for(auto& c : result)
  66. if(c == '/')
  67. c = path_separator;
  68. #else
  69. char constexpr path_separator = '/';
  70. if(result.back() == path_separator)
  71. result.resize(result.size() - 1);
  72. result.append(path.data(), path.size());
  73. #endif
  74. return result;
  75. }
  76. // This function produces an HTTP response for the given
  77. // request. The type of the response object depends on the
  78. // contents of the request, so the interface requires the
  79. // caller to pass a generic lambda for receiving the response.
  80. template<
  81. class Body, class Allocator,
  82. class Send>
  83. void
  84. handle_request(
  85. beast::string_view doc_root,
  86. http::request<Body, http::basic_fields<Allocator>>&& req,
  87. Send&& send)
  88. {
  89. // Returns a bad request response
  90. auto const bad_request =
  91. [&req](beast::string_view why)
  92. {
  93. http::response<http::string_body> res{http::status::bad_request, req.version()};
  94. res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  95. res.set(http::field::content_type, "text/html");
  96. res.keep_alive(req.keep_alive());
  97. res.body() = std::string(why);
  98. res.prepare_payload();
  99. return res;
  100. };
  101. // Returns a not found response
  102. auto const not_found =
  103. [&req](beast::string_view target)
  104. {
  105. http::response<http::string_body> res{http::status::not_found, req.version()};
  106. res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  107. res.set(http::field::content_type, "text/html");
  108. res.keep_alive(req.keep_alive());
  109. res.body() = "The resource '" + std::string(target) + "' was not found.";
  110. res.prepare_payload();
  111. return res;
  112. };
  113. // Returns a server error response
  114. auto const server_error =
  115. [&req](beast::string_view what)
  116. {
  117. http::response<http::string_body> res{http::status::internal_server_error, req.version()};
  118. res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  119. res.set(http::field::content_type, "text/html");
  120. res.keep_alive(req.keep_alive());
  121. res.body() = "An error occurred: '" + std::string(what) + "'";
  122. res.prepare_payload();
  123. return res;
  124. };
  125. // Make sure we can handle the method
  126. if( req.method() != http::verb::get &&
  127. req.method() != http::verb::head)
  128. return send(bad_request("Unknown HTTP-method"));
  129. // Request path must be absolute and not contain "..".
  130. if( req.target().empty() ||
  131. req.target()[0] != '/' ||
  132. req.target().find("..") != beast::string_view::npos)
  133. return send(bad_request("Illegal request-target"));
  134. // Build the path to the requested file
  135. std::string path = path_cat(doc_root, req.target());
  136. if(req.target().back() == '/')
  137. path.append("index.html");
  138. // Attempt to open the file
  139. beast::error_code ec;
  140. http::file_body::value_type body;
  141. body.open(path.c_str(), beast::file_mode::scan, ec);
  142. // Handle the case where the file doesn't exist
  143. if(ec == boost::system::errc::no_such_file_or_directory)
  144. return send(not_found(req.target()));
  145. // Handle an unknown error
  146. if(ec)
  147. return send(server_error(ec.message()));
  148. // Cache the size since we need it after the move
  149. auto const size = body.size();
  150. // Respond to HEAD request
  151. if(req.method() == http::verb::head)
  152. {
  153. http::response<http::empty_body> res{http::status::ok, req.version()};
  154. res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  155. res.set(http::field::content_type, mime_type(path));
  156. res.content_length(size);
  157. res.keep_alive(req.keep_alive());
  158. return send(std::move(res));
  159. }
  160. // Respond to GET request
  161. http::response<http::file_body> res{
  162. std::piecewise_construct,
  163. std::make_tuple(std::move(body)),
  164. std::make_tuple(http::status::ok, req.version())};
  165. res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  166. res.set(http::field::content_type, mime_type(path));
  167. res.content_length(size);
  168. res.keep_alive(req.keep_alive());
  169. return send(std::move(res));
  170. }
  171. //------------------------------------------------------------------------------
  172. struct http_session::send_lambda
  173. {
  174. http_session& self_;
  175. explicit
  176. send_lambda(http_session& self)
  177. : self_(self)
  178. {
  179. }
  180. template<bool isRequest, class Body, class Fields>
  181. void
  182. operator()(http::message<isRequest, Body, Fields>&& msg) const
  183. {
  184. // The lifetime of the message has to extend
  185. // for the duration of the async operation so
  186. // we use a shared_ptr to manage it.
  187. auto sp = boost::make_shared<
  188. http::message<isRequest, Body, Fields>>(std::move(msg));
  189. // Write the response
  190. auto self = self_.shared_from_this();
  191. http::async_write(
  192. self_.stream_,
  193. *sp,
  194. [self, sp](beast::error_code ec, std::size_t bytes)
  195. {
  196. self->on_write(ec, bytes, sp->need_eof());
  197. });
  198. }
  199. };
  200. //------------------------------------------------------------------------------
  201. http_session::
  202. http_session(
  203. tcp::socket&& socket,
  204. boost::shared_ptr<shared_state> const& state)
  205. : stream_(std::move(socket))
  206. , state_(state)
  207. {
  208. }
  209. void
  210. http_session::
  211. run()
  212. {
  213. do_read();
  214. }
  215. // Report a failure
  216. void
  217. http_session::
  218. fail(beast::error_code ec, char const* what)
  219. {
  220. // Don't report on canceled operations
  221. if(ec == net::error::operation_aborted)
  222. return;
  223. std::cerr << what << ": " << ec.message() << "\n";
  224. }
  225. void
  226. http_session::
  227. do_read()
  228. {
  229. // Construct a new parser for each message
  230. parser_.emplace();
  231. // Apply a reasonable limit to the allowed size
  232. // of the body in bytes to prevent abuse.
  233. parser_->body_limit(10000);
  234. // Set the timeout.
  235. stream_.expires_after(std::chrono::seconds(30));
  236. // Read a request
  237. http::async_read(
  238. stream_,
  239. buffer_,
  240. parser_->get(),
  241. beast::bind_front_handler(
  242. &http_session::on_read,
  243. shared_from_this()));
  244. }
  245. void
  246. http_session::
  247. on_read(beast::error_code ec, std::size_t)
  248. {
  249. // This means they closed the connection
  250. if(ec == http::error::end_of_stream)
  251. {
  252. stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
  253. return;
  254. }
  255. // Handle the error, if any
  256. if(ec)
  257. return fail(ec, "read");
  258. // See if it is a WebSocket Upgrade
  259. if(websocket::is_upgrade(parser_->get()))
  260. {
  261. // Create a websocket session, transferring ownership
  262. // of both the socket and the HTTP request.
  263. boost::make_shared<websocket_session>(
  264. stream_.release_socket(),
  265. state_)->run(parser_->release());
  266. return;
  267. }
  268. // Send the response
  269. #ifndef BOOST_NO_CXX14_GENERIC_LAMBDAS
  270. //
  271. // The following code requires generic
  272. // lambdas, available in C++14 and later.
  273. //
  274. handle_request(
  275. state_->doc_root(),
  276. std::move(req_),
  277. [this](auto&& response)
  278. {
  279. // The lifetime of the message has to extend
  280. // for the duration of the async operation so
  281. // we use a shared_ptr to manage it.
  282. using response_type = typename std::decay<decltype(response)>::type;
  283. auto sp = boost::make_shared<response_type>(std::forward<decltype(response)>(response));
  284. #if 0
  285. // NOTE This causes an ICE in gcc 7.3
  286. // Write the response
  287. http::async_write(this->stream_, *sp,
  288. [self = shared_from_this(), sp](
  289. beast::error_code ec, std::size_t bytes)
  290. {
  291. self->on_write(ec, bytes, sp->need_eof());
  292. });
  293. #else
  294. // Write the response
  295. auto self = shared_from_this();
  296. http::async_write(stream_, *sp,
  297. [self, sp](
  298. beast::error_code ec, std::size_t bytes)
  299. {
  300. self->on_write(ec, bytes, sp->need_eof());
  301. });
  302. #endif
  303. });
  304. #else
  305. //
  306. // This code uses the function object type send_lambda in
  307. // place of a generic lambda which is not available in C++11
  308. //
  309. handle_request(
  310. state_->doc_root(),
  311. parser_->release(),
  312. send_lambda(*this));
  313. #endif
  314. }
  315. void
  316. http_session::
  317. on_write(beast::error_code ec, std::size_t, bool close)
  318. {
  319. // Handle the error, if any
  320. if(ec)
  321. return fail(ec, "write");
  322. if(close)
  323. {
  324. // This means we should close the connection, usually because
  325. // the response indicated the "Connection: close" semantic.
  326. stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
  327. return;
  328. }
  329. // Read another request
  330. do_read();
  331. }