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

// this file is derived from a boost::beast sample
#include <boost/beast/core.hpp>
#include <boost/filesystem.hpp>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
namespace fs = boost::filesystem;
#include "webserver.hh"
namespace pEp {
Webserver::Webserver(net::ip::address addr, unsigned short port, const std::string& doc_root)
: _ioc{1}
, _acceptor{_ioc, {addr, port}, false}
, _doc_root{doc_root}
, _generic_handler{}
, _port{port}
, _running{false}
{ }
beast::string_view Webserver::mime_type(beast::string_view path)
{
using beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == beast::string_view::npos)
return beast::string_view{};
return path.substr(pos);
}();
if(iequals(ext, ".htm")) return "text/html; charset=utf-8";
if(iequals(ext, ".html")) return "text/html; charset=utf-8";
if(iequals(ext, ".css")) return "text/css; charset=utf-8";
if(iequals(ext, ".txt")) return "text/plain; charset=utf-8";
if(iequals(ext, ".js")) return "application/javascript; charset=utf-8";
if(iequals(ext, ".json")) return "application/json; charset=utf-8";
if(iequals(ext, ".xml")) return "application/xml; charset=utf-8";
if(iequals(ext, ".png")) return "image/png";
if(iequals(ext, ".jpeg")) return "image/jpeg";
if(iequals(ext, ".jpg")) return "image/jpeg";
if(iequals(ext, ".gif")) return "image/gif";
if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
if(iequals(ext, ".tiff")) return "image/tiff";
if(iequals(ext, ".tif")) return "image/tiff";
if(iequals(ext, ".svg")) return "image/svg+xml; charset=utf-8";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/octet-stream";
}
void Webserver::add_url_handler(const std::string& url_regex, handler_t handler)
{
std::lock_guard< std::mutex > lock(_mtx);
_urls.emplace(url_regex, Handling{boost::regex(url_regex), handler});
}
void Webserver::remove_url_handler(const std::string& url_regex)
{
std::lock_guard< std::mutex > lock(_mtx);
_urls.erase(url_regex);
}
void Webserver::add_generic_url_handler(handler_t handler)
{
_generic_handler = handler;
}
void Webserver::remove_generic_url_handler()
{
_generic_handler = nullptr;
}
Webserver::response Webserver::create_status_response(const request& req, http::status status)
{
http::response< http::string_body > res{status, req.version()};
res.set(http::field::content_type, "text/html; charset=utf-8");
res.keep_alive(req.keep_alive());
std::stringstream s;
s << "<html><body>" << int(status) << " " << status << "</body></html>";
res.body() = s.str();
res.prepare_payload();
if (status != http::status::internal_server_error)
res.keep_alive(req.keep_alive());
return res;
}
Webserver* Webserver::probing_port_range(net::ip::address addr, unsigned short
start, unsigned short end, unsigned short& port, const std::string&
doc_root)
{
pEp::Webserver *web = nullptr;
for (port = start; port <= end; ++port) {
try {
web = new pEp::Webserver{addr, port, doc_root};
break;
}
catch (boost::system::system_error& err) {
}
}
return web;
}
void Webserver::deliver_status(tcp::socket *socket, const request& req, http::status status)
{
const response res { create_status_response(req, status) };
beast::error_code ec;
http::write(*socket, res, ec);
}
void Webserver::deliver_file(tcp::socket *socket, const request& req)
{
static boost::regex file{"/([\\w\\d]{1,100}\\.[\\w\\d]{1,4})"};
boost::cmatch m;
std::string d{req.target().data(), req.target().length()};
if (boost::regex_match(d.c_str(), m, file)) {
fs::path p{_doc_root};
p /= std::string(m[1]);
beast::error_code ec;
http::file_body::value_type body;
body.open(p.string().c_str(), beast::file_mode::scan, ec);
if (ec == beast::errc::no_such_file_or_directory) {
deliver_status(socket, req, http::status::not_found);
}
else if (ec) {
deliver_status(socket, req, http::status::internal_server_error);
}
else {
auto const size = body.size();
http::response<http::file_body> res{
std::piecewise_construct,
std::make_tuple(std::move(body)),
std::make_tuple(http::status::ok, req.version())};
res.set(http::field::content_type, mime_type(p.string().c_str()));
res.content_length(size);
res.keep_alive(req.keep_alive());
http::response_serializer<http::file_body> res_ser{res};
http::write(*socket, res_ser, ec);
}
}
else {
deliver_status(socket, req, http::status::not_found);
}
}
Webserver::handler_t Webserver::find_handler(const request& req, boost::cmatch& m)
{
std::lock_guard< std::mutex > lock(_mtx);
for (auto it=_urls.begin(); it!=_urls.end(); ++it) {
std::string d{req.target().data(), req.target().length()};
if (boost::regex_match(d.c_str(), m, it->second.regex))
return it->second.handler;
}
return _generic_handler; // might be empty std::function!
}
void Webserver::do_session(tcp::socket *socket)
{
beast::error_code ec;
beast::flat_buffer buffer;
while (_running)
{
http::request_parser<http::string_body> parser;
parser.body_limit((std::numeric_limits<std::int32_t>::max)());
http::read(*socket, buffer, parser, ec);
if (ec) {
#ifndef NDEBUG
// This will print stuff like ...
// - end of stream
// - An established connection was aborted by the software in your host machine
// ... so don't be alarmed.
std::cerr << "pEpWebserver: " << ec.message() << "\n";
#endif
goto the_end;
}
http::request<http::string_body> req = parser.get();
const auto method = req.method();
switch (method)
{
case http::verb::post: // fall through
case http::verb::get:
{
boost::cmatch m;
Webserver::handler_t handler = find_handler(req, m);
if (handler) {
try{
const Webserver::response res = handler(m, req);
http::write(*socket, res, ec);
}
catch(...){
deliver_status(socket, req, http::status::internal_server_error);
}
}
else {
if(method == http::verb::get && !_doc_root.empty())
{
deliver_file(socket, req);
}else{
deliver_status(socket, req, http::status::not_found);
}
}
break;
}
default:
deliver_status(socket, req, http::status::method_not_allowed);
};
if (ec)
break;
}
the_end:
socket->shutdown(tcp::socket::shutdown_send, ec);
delete socket;
}
void Webserver::runner(Webserver *me)
{
while (me->_running)
{
tcp::socket* socket = new tcp::socket{me->_ioc};
me->_acceptor.accept(*socket);
std::function< void() > tf = [=]()
{
me->thread_init();
me->do_session(socket);
me->thread_done();
};
std::thread{tf}.detach();
}
}
void Webserver::run()
{
_running = true;
_runner = std::thread(runner, this);
}
void Webserver::shutdown()
{
_running = false;
}
};