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.

270 lines
7.7 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}, 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::response_serializer<http::file_body> res_ser{res};
  126. http::write(*socket, res_ser, ec);
  127. }
  128. }
  129. else {
  130. deliver_status(socket, req, http::status::not_found);
  131. }
  132. }
  133. Webserver::handler_t Webserver::find_handler(const request& req, boost::cmatch& m)
  134. {
  135. std::lock_guard< std::mutex > lock(_mtx);
  136. for (auto it=_urls.begin(); it!=_urls.end(); ++it) {
  137. std::string d{req.target().data(), req.target().length()};
  138. if (boost::regex_match(d.c_str(), m, it->second.regex))
  139. return it->second.handler;
  140. }
  141. return _generic_handler; // might be empty std::function!
  142. }
  143. void Webserver::do_session(tcp::socket *socket)
  144. {
  145. beast::error_code ec;
  146. beast::flat_buffer buffer;
  147. while (_running)
  148. {
  149. http::request_parser<http::string_body> parser;
  150. parser.body_limit((std::numeric_limits<std::int32_t>::max)());
  151. http::read(*socket, buffer, parser, ec);
  152. if (ec) {
  153. #ifndef NDEBUG
  154. // This will print stuff like ...
  155. // - end of stream
  156. // - An established connection was aborted by the software in your host machine
  157. // ... so don't be alarmed.
  158. std::cerr << "pEpWebserver: " << ec.message() << "\n";
  159. #endif
  160. goto the_end;
  161. }
  162. http::request<http::string_body> req = parser.get();
  163. const auto method = req.method();
  164. switch (method)
  165. {
  166. case http::verb::post: // fall through
  167. case http::verb::get:
  168. {
  169. boost::cmatch m;
  170. Webserver::handler_t handler = find_handler(req, m);
  171. if (handler) {
  172. try{
  173. const Webserver::response res = handler(m, req);
  174. http::write(*socket, res, ec);
  175. }
  176. catch(...){
  177. deliver_status(socket, req, http::status::internal_server_error);
  178. }
  179. }
  180. else {
  181. if(method == http::verb::get && !_doc_root.empty())
  182. {
  183. deliver_file(socket, req);
  184. }else{
  185. deliver_status(socket, req, http::status::not_found);
  186. }
  187. }
  188. break;
  189. }
  190. default:
  191. deliver_status(socket, req, http::status::method_not_allowed);
  192. };
  193. if (ec)
  194. break;
  195. }
  196. the_end:
  197. socket->shutdown(tcp::socket::shutdown_send, ec);
  198. delete socket;
  199. }
  200. void Webserver::runner(Webserver *me)
  201. {
  202. while (me->_running)
  203. {
  204. tcp::socket* socket = new tcp::socket{me->_ioc};
  205. me->_acceptor.accept(*socket);
  206. std::function< void() > tf = [=]()
  207. {
  208. me->thread_init();
  209. me->do_session(socket);
  210. me->thread_done();
  211. };
  212. std::thread{tf}.detach();
  213. }
  214. }
  215. void Webserver::run()
  216. {
  217. _running = true;
  218. _runner = std::thread(runner, this);
  219. }
  220. void Webserver::shutdown()
  221. {
  222. _running = false;
  223. }
  224. };