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.

263 lines
7.4KB

  1. // this file is derived from a boost::beast sample
  2. #include <boost/beast/core.hpp>
  3. #include <boost/filesystem.hpp>
  4. #include <cstdlib>
  5. #include <iostream>
  6. #include <memory>
  7. #include <string>
  8. namespace fs = boost::filesystem;
  9. #include "webserver.hh"
  10. namespace pEp {
  11. Webserver::Webserver(net::ip::address addr, unsigned short port, const std::string& doc_root)
  12. : _ioc{1}
  13. , _acceptor{_ioc, {addr, port}, false}
  14. , _doc_root{doc_root}
  15. , _generic_handler{}
  16. , _port{port}
  17. , _running{false}
  18. { }
  19. beast::string_view Webserver::mime_type(beast::string_view path)
  20. {
  21. using beast::iequals;
  22. auto const ext = [&path]
  23. {
  24. auto const pos = path.rfind(".");
  25. if(pos == beast::string_view::npos)
  26. return beast::string_view{};
  27. return path.substr(pos);
  28. }();
  29. if(iequals(ext, ".htm")) return "text/html; charset=utf-8";
  30. if(iequals(ext, ".html")) return "text/html; charset=utf-8";
  31. if(iequals(ext, ".css")) return "text/css; charset=utf-8";
  32. if(iequals(ext, ".txt")) return "text/plain; charset=utf-8";
  33. if(iequals(ext, ".js")) return "application/javascript; charset=utf-8";
  34. if(iequals(ext, ".json")) return "application/json; charset=utf-8";
  35. if(iequals(ext, ".xml")) return "application/xml; charset=utf-8";
  36. if(iequals(ext, ".png")) return "image/png";
  37. if(iequals(ext, ".jpeg")) return "image/jpeg";
  38. if(iequals(ext, ".jpg")) return "image/jpeg";
  39. if(iequals(ext, ".gif")) return "image/gif";
  40. if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
  41. if(iequals(ext, ".tiff")) return "image/tiff";
  42. if(iequals(ext, ".tif")) return "image/tiff";
  43. if(iequals(ext, ".svg")) return "image/svg+xml; charset=utf-8";
  44. if(iequals(ext, ".svgz")) return "image/svg+xml";
  45. return "application/octet-stream";
  46. }
  47. void Webserver::add_url_handler(const std::string& url_regex, handler_t handler)
  48. {
  49. std::lock_guard< std::mutex > lock(_mtx);
  50. _urls.emplace(url_regex, Handling{boost::regex(url_regex), handler});
  51. }
  52. void Webserver::remove_url_handler(const std::string& url_regex)
  53. {
  54. std::lock_guard< std::mutex > lock(_mtx);
  55. _urls.erase(url_regex);
  56. }
  57. void Webserver::add_generic_url_handler(handler_t handler)
  58. {
  59. _generic_handler = handler;
  60. }
  61. void Webserver::remove_generic_url_handler()
  62. {
  63. _generic_handler = nullptr;
  64. }
  65. Webserver::response Webserver::create_status_response(const request& req, http::status status)
  66. {
  67. http::response< http::string_body > res{status, req.version()};
  68. res.set(http::field::content_type, "text/html; charset=utf-8");
  69. res.keep_alive(req.keep_alive());
  70. std::stringstream s;
  71. s << "<html><body>" << int(status) << " " << status << "</body></html>";
  72. res.body() = s.str();
  73. res.prepare_payload();
  74. if (status != http::status::internal_server_error)
  75. res.keep_alive(req.keep_alive());
  76. return res;
  77. }
  78. Webserver* Webserver::probing_port_range(net::ip::address addr, unsigned short
  79. start, unsigned short end, unsigned short& port, const std::string&
  80. doc_root)
  81. {
  82. pEp::Webserver *web = nullptr;
  83. for (port = start; port <= end; ++port) {
  84. try {
  85. web = new pEp::Webserver{addr, port, doc_root};
  86. break;
  87. }
  88. catch (boost::system::system_error& err) {
  89. }
  90. }
  91. return web;
  92. }
  93. void Webserver::deliver_status(tcp::socket *socket, const request& req, http::status status)
  94. {
  95. const response res { create_status_response(req, status) };
  96. beast::error_code ec;
  97. http::write(*socket, res, ec);
  98. }
  99. void Webserver::deliver_file(tcp::socket *socket, const request& req)
  100. {
  101. static boost::regex file{"/([\\w\\d]{1,100}\\.[\\w\\d]{1,4})"};
  102. boost::cmatch m;
  103. std::string d{req.target().data(), req.target().length()};
  104. if (boost::regex_match(d.c_str(), m, file)) {
  105. fs::path p{_doc_root};
  106. p /= std::string(m[1]);
  107. beast::error_code ec;
  108. http::file_body::value_type body;
  109. body.open(p.string().c_str(), beast::file_mode::scan, ec);
  110. if (ec == beast::errc::no_such_file_or_directory) {
  111. deliver_status(socket, req, http::status::not_found);
  112. }
  113. else if (ec) {
  114. deliver_status(socket, req, http::status::internal_server_error);
  115. }
  116. else {
  117. auto const size = body.size();
  118. http::response<http::file_body> res{
  119. std::piecewise_construct,
  120. std::make_tuple(std::move(body)),
  121. std::make_tuple(http::status::ok, req.version())};
  122. res.set(http::field::content_type, mime_type(p.string().c_str()));
  123. res.content_length(size);
  124. res.keep_alive(req.keep_alive());
  125. http::write(*socket, res, ec);
  126. }
  127. }
  128. else {
  129. deliver_status(socket, req, http::status::not_found);
  130. }
  131. }
  132. Webserver::handler_t Webserver::find_handler(const request& req, boost::cmatch& m)
  133. {
  134. std::lock_guard< std::mutex > lock(_mtx);
  135. for (auto it=_urls.begin(); it!=_urls.end(); ++it) {
  136. std::string d{req.target().data(), req.target().length()};
  137. if (boost::regex_match(d.c_str(), m, it->second.regex))
  138. return it->second.handler;
  139. }
  140. return _generic_handler; // might be empty std::function!
  141. }
  142. void Webserver::do_session(tcp::socket *socket)
  143. {
  144. beast::error_code ec;
  145. beast::flat_buffer buffer;
  146. while (_running)
  147. {
  148. http::request_parser<http::string_body> parser;
  149. parser.body_limit((std::numeric_limits<std::int32_t>::max)());
  150. http::read(*socket, buffer, parser, ec);
  151. if (ec) {
  152. std::cerr << ec.message() << "\n";
  153. goto the_end;
  154. }
  155. http::request<http::string_body> req = parser.get();
  156. const auto method = req.method();
  157. switch (method)
  158. {
  159. case http::verb::post: // fall through
  160. case http::verb::get:
  161. {
  162. boost::cmatch m;
  163. Webserver::handler_t handler = find_handler(req, m);
  164. if (handler) {
  165. try{
  166. const Webserver::response res = handler(m, req);
  167. http::write(*socket, res, ec);
  168. }
  169. catch(...){
  170. deliver_status(socket, req, http::status::internal_server_error);
  171. }
  172. }
  173. else {
  174. if(method == http::verb::get && !_doc_root.empty())
  175. {
  176. deliver_file(socket, req);
  177. }else{
  178. deliver_status(socket, req, http::status::not_found);
  179. }
  180. }
  181. break;
  182. }
  183. default:
  184. deliver_status(socket, req, http::status::method_not_allowed);
  185. };
  186. if (ec)
  187. break;
  188. }
  189. the_end:
  190. socket->shutdown(tcp::socket::shutdown_send, ec);
  191. delete socket;
  192. }
  193. void Webserver::runner(Webserver *me)
  194. {
  195. while (me->_running)
  196. {
  197. tcp::socket* socket = new tcp::socket{me->_ioc};
  198. me->_acceptor.accept(*socket);
  199. std::function< void() > tf = [=]()
  200. {
  201. me->thread_init();
  202. me->do_session(socket);
  203. me->thread_done();
  204. };
  205. std::thread{tf}.detach();
  206. }
  207. }
  208. void Webserver::run()
  209. {
  210. _running = true;
  211. _runner = std::thread(runner, this);
  212. }
  213. void Webserver::shutdown()
  214. {
  215. _running = false;
  216. }
  217. };