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.

255 lines
7.2 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
  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}}
  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<http::string_body> req;
  149. http::read(*socket, buffer, req, ec);
  150. if (ec)
  151. break;
  152. const auto method = req.method();
  153. switch (method)
  154. {
  155. case http::verb::post: // fall through
  156. case http::verb::get:
  157. {
  158. boost::cmatch m;
  159. Webserver::handler_t handler = find_handler(req, m);
  160. if (handler) {
  161. try{
  162. const Webserver::response res = handler(m, req);
  163. http::write(*socket, res, ec);
  164. }
  165. catch(...){
  166. deliver_status(socket, req, http::status::internal_server_error);
  167. }
  168. }
  169. else {
  170. if(method == http::verb::get && !_doc_root.empty())
  171. {
  172. deliver_file(socket, req);
  173. }else{
  174. deliver_status(socket, req, http::status::not_found);
  175. }
  176. }
  177. break;
  178. }
  179. default:
  180. deliver_status(socket, req, http::status::method_not_allowed);
  181. };
  182. if (ec)
  183. break;
  184. }
  185. socket->shutdown(tcp::socket::shutdown_send, ec);
  186. delete socket;
  187. }
  188. void Webserver::runner(Webserver *me)
  189. {
  190. while (me->_running)
  191. {
  192. tcp::socket* socket = new tcp::socket{me->_ioc};
  193. me->_acceptor.accept(*socket);
  194. std::function< void() > tf = [=]()
  195. {
  196. me->thread_init();
  197. me->do_session(socket);
  198. me->thread_done();
  199. };
  200. std::thread{tf}.detach();
  201. }
  202. }
  203. void Webserver::run()
  204. {
  205. _running = true;
  206. _runner = std::thread(runner, this);
  207. }
  208. void Webserver::shutdown()
  209. {
  210. _running = false;
  211. }
  212. };