a simple multithreaded webserver
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

196 lines
5.8 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. // this file is derived from a boost::beast sample
  2. #include <boost/beast/core.hpp>
  3. #include <boost/filesystem/path.hpp>
  4. #include <cstdlib>
  5. #include <iostream>
  6. #include <memory>
  7. #include <string>
  8. #include <thread>
  9. namespace fs = boost::filesystem;
  10. #include "webserver.hh"
  11. namespace pEp {
  12. Webserver::Webserver(net::ip::address addr, unsigned short port, std::string doc_root)
  13. : _ioc(1), _acceptor(_ioc, {addr, port}), _doc_root(doc_root), _running(false) { }
  14. static beast::string_view mime_type(beast::string_view path)
  15. {
  16. using beast::iequals;
  17. auto const ext = [&path]
  18. {
  19. auto const pos = path.rfind(".");
  20. if(pos == beast::string_view::npos)
  21. return beast::string_view{};
  22. return path.substr(pos);
  23. }();
  24. if(iequals(ext, ".htm")) return "text/html";
  25. if(iequals(ext, ".html")) return "text/html";
  26. if(iequals(ext, ".css")) return "text/css";
  27. if(iequals(ext, ".txt")) return "text/plain";
  28. if(iequals(ext, ".js")) return "application/javascript";
  29. if(iequals(ext, ".json")) return "application/json";
  30. if(iequals(ext, ".xml")) return "application/xml";
  31. if(iequals(ext, ".png")) return "image/png";
  32. if(iequals(ext, ".jpeg")) return "image/jpeg";
  33. if(iequals(ext, ".jpg")) return "image/jpeg";
  34. if(iequals(ext, ".gif")) return "image/gif";
  35. if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
  36. if(iequals(ext, ".tiff")) return "image/tiff";
  37. if(iequals(ext, ".tif")) return "image/tiff";
  38. if(iequals(ext, ".svg")) return "image/svg+xml";
  39. if(iequals(ext, ".svgz")) return "image/svg+xml";
  40. return "application/octet-stream";
  41. }
  42. static void fail(beast::error_code ec, char const* what)
  43. {
  44. std::cerr << what << ": " << ec.message() << "\n";
  45. ;}
  46. void Webserver::add_url_handler(std::string url_regex, handler_t handler)
  47. {
  48. std::lock_guard< std::mutex > lock(_mtx);
  49. _urls.emplace(std::pair< std::string, Handling >(url_regex, {boost::regex(url_regex), handler}));
  50. }
  51. void Webserver::remove_url_handler(std::string url_regex) {
  52. std::lock_guard< std::mutex > lock(_mtx);
  53. _urls.erase(url_regex);
  54. }
  55. void Webserver::deliver_status(tcp::socket *socket, Webserver::request req, http::status status)
  56. {
  57. http::response< http::string_body > res{status, req.version()};
  58. res.set(http::field::content_type, "text/html");
  59. res.keep_alive(req.keep_alive());
  60. res.prepare_payload();
  61. if (status != http::status::internal_server_error)
  62. res.keep_alive(req.keep_alive());
  63. beast::error_code ec;
  64. http::write(*socket, res, ec);
  65. }
  66. void Webserver::deliver_file(tcp::socket *socket, Webserver::request req)
  67. {
  68. static boost::regex file{"/([\\w\\d]{1-100}\\.[\\w\\d]{1-4})"};
  69. boost::cmatch m;
  70. if (boost::regex_match(req.target().data(), m, file)) {
  71. fs::path p{_doc_root};
  72. p += m[1];
  73. beast::error_code ec;
  74. http::file_body::value_type body;
  75. body.open(p.c_str(), beast::file_mode::scan, ec);
  76. if (ec == beast::errc::no_such_file_or_directory) {
  77. deliver_status(socket, req, http::status::not_found);
  78. }
  79. else if (ec) {
  80. deliver_status(socket, req, http::status::internal_server_error);
  81. }
  82. else {
  83. auto const size = body.size();
  84. http::response<http::file_body> res{
  85. std::piecewise_construct,
  86. std::make_tuple(std::move(body)),
  87. std::make_tuple(http::status::ok, req.version())};
  88. res.set(http::field::content_type, mime_type(p.c_str()));
  89. res.content_length(size);
  90. res.keep_alive(req.keep_alive());
  91. http::write(*socket, res, ec);
  92. }
  93. }
  94. else {
  95. deliver_status(socket, req, http::status::not_found);
  96. }
  97. }
  98. Webserver::handler_t Webserver::find_handler(request& r, boost::cmatch& m)
  99. {
  100. std::lock_guard< std::mutex > lock(_mtx);
  101. for (auto it=_urls.begin(); it!=_urls.end(); ++it) {
  102. if (boost::regex_match(r.target().data(), m, it->second.regex))
  103. return it->second.handler;
  104. }
  105. return nullptr;
  106. }
  107. void Webserver::do_session(tcp::socket *socket)
  108. {
  109. beast::error_code ec;
  110. beast::flat_buffer buffer;
  111. while (_running)
  112. {
  113. http::request<http::string_body> req;
  114. http::read(*socket, buffer, req, ec);
  115. if (ec == http::error::end_of_stream)
  116. break;
  117. if (ec) {
  118. delete socket;
  119. return fail(ec, "reading from stream");
  120. }
  121. switch (req.method()) {
  122. case http::verb::post: {
  123. boost::cmatch m;
  124. Webserver::handler_t handler = find_handler(req, m);
  125. if (handler) {
  126. Webserver::response *res = handler(m, req);
  127. if (!res) {
  128. deliver_status(socket, req, http::status::not_found);
  129. }
  130. else {
  131. http::write(*socket, *res, ec);
  132. delete res;
  133. }
  134. }
  135. else {
  136. deliver_status(socket, req, http::status::not_found);
  137. }
  138. }
  139. break;
  140. case http::verb::get:
  141. deliver_file(socket, req);
  142. break;
  143. default:
  144. deliver_status(socket, req, http::status::method_not_allowed);
  145. };
  146. if (ec) {
  147. delete socket;
  148. return fail(ec, "writing to stream");
  149. }
  150. }
  151. socket->shutdown(tcp::socket::shutdown_send, ec);
  152. delete socket;
  153. }
  154. void Webserver::run()
  155. {
  156. _running = true;
  157. while (_running)
  158. {
  159. tcp::socket* socket = new tcp::socket{_ioc};
  160. _acceptor.accept(*socket);
  161. std::function< void() > tf = [&](){do_session(socket);};
  162. std::thread{tf}.detach();
  163. }
  164. }
  165. void Webserver::shutdown()
  166. {
  167. _running = false;
  168. }
  169. };