webserver/webserver.cc

271 lines
7.7 KiB
C++
Raw Normal View History

2020-06-08 13:01:56 +02:00
// this file is derived from a boost::beast sample
2020-06-08 11:05:33 +02:00
#include <boost/beast/core.hpp>
2020-06-08 21:45:55 +02:00
#include <boost/filesystem.hpp>
2020-06-08 11:05:33 +02:00
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
2020-06-08 17:55:47 +02:00
namespace fs = boost::filesystem;
2020-06-08 11:05:33 +02:00
2020-06-08 13:01:56 +02:00
#include "webserver.hh"
2020-06-08 11:05:33 +02:00
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}
{ }
2020-06-08 22:00:49 +02:00
beast::string_view Webserver::mime_type(beast::string_view path)
2020-06-08 11:05:33 +02:00
{
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);
}();
2020-06-08 21:45:55 +02:00
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";
2020-06-08 11:05:33 +02:00
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";
2020-06-08 21:45:55 +02:00
if(iequals(ext, ".svg")) return "image/svg+xml; charset=utf-8";
2020-06-08 11:05:33 +02:00
if(iequals(ext, ".svgz")) return "image/svg+xml";
2020-06-08 19:24:57 +02:00
return "application/octet-stream";
2020-06-08 11:05:33 +02:00
}
void Webserver::add_url_handler(const std::string& url_regex, handler_t handler)
2020-06-08 17:02:48 +02:00
{
std::lock_guard< std::mutex > lock(_mtx);
_urls.emplace(url_regex, Handling{boost::regex(url_regex), handler});
2020-06-08 11:05:33 +02:00
}
void Webserver::remove_url_handler(const std::string& url_regex)
{
2020-06-08 17:02:48 +02:00
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)
2020-06-08 18:48:27 +02:00
{
2020-06-08 19:24:57 +02:00
http::response< http::string_body > res{status, req.version()};
2020-06-08 23:12:05 +02:00
res.set(http::field::content_type, "text/html; charset=utf-8");
2020-06-08 18:27:37 +02:00
res.keep_alive(req.keep_alive());
2020-06-08 21:45:55 +02:00
std::stringstream s;
s << "<html><body>" << int(status) << " " << status << "</body></html>";
2020-06-08 21:45:55 +02:00
res.body() = s.str();
2020-06-08 18:27:37 +02:00
res.prepare_payload();
2020-06-08 19:24:57 +02:00
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) };
2020-06-08 19:24:57 +02:00
beast::error_code ec;
http::write(*socket, res, ec);
2020-06-08 18:27:37 +02:00
}
void Webserver::deliver_file(tcp::socket *socket, const request& req)
2020-06-08 17:09:43 +02:00
{
2020-06-08 21:45:55 +02:00
static boost::regex file{"/([\\w\\d]{1,100}\\.[\\w\\d]{1,4})"};
2020-06-08 17:55:47 +02:00
boost::cmatch m;
2020-06-08 23:12:05 +02:00
std::string d{req.target().data(), req.target().length()};
2020-06-08 21:45:55 +02:00
if (boost::regex_match(d.c_str(), m, file)) {
2020-06-08 17:55:47 +02:00
fs::path p{_doc_root};
2020-06-13 09:06:14 +02:00
p /= std::string(m[1]);
2020-06-08 19:24:57 +02:00
beast::error_code ec;
http::file_body::value_type body;
2020-06-13 09:06:14 +02:00
body.open(p.string().c_str(), beast::file_mode::scan, ec);
2020-06-08 19:24:57 +02:00
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())};
2020-06-13 09:06:14 +02:00
res.set(http::field::content_type, mime_type(p.string().c_str()));
2020-06-08 19:24:57 +02:00
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);
2020-06-08 19:24:57 +02:00
}
2020-06-08 17:55:47 +02:00
}
else {
2020-06-08 19:24:57 +02:00
deliver_status(socket, req, http::status::not_found);
2020-06-08 17:55:47 +02:00
}
2020-06-08 17:09:43 +02:00
}
Webserver::handler_t Webserver::find_handler(const request& req, boost::cmatch& m)
2020-06-08 11:05:33 +02:00
{
2020-06-08 17:55:47 +02:00
std::lock_guard< std::mutex > lock(_mtx);
2020-06-08 17:02:48 +02:00
for (auto it=_urls.begin(); it!=_urls.end(); ++it) {
2020-06-08 23:12:05 +02:00
std::string d{req.target().data(), req.target().length()};
if (boost::regex_match(d.c_str(), m, it->second.regex))
2020-06-08 17:02:48 +02:00
return it->second.handler;
}
2020-06-08 11:05:33 +02:00
return _generic_handler; // might be empty std::function!
2020-06-08 17:02:48 +02:00
}
2020-06-08 11:05:33 +02:00
2020-06-08 17:02:48 +02:00
void Webserver::do_session(tcp::socket *socket)
{
beast::error_code ec;
2020-06-22 13:33:47 +02:00
beast::flat_buffer buffer;
2020-06-08 11:05:33 +02:00
2020-06-08 15:24:07 +02:00
while (_running)
2020-06-08 11:05:33 +02:00
{
2020-06-22 13:33:47 +02:00
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
2020-06-22 13:33:47 +02:00
goto the_end;
}
http::request<http::string_body> req = parser.get();
2020-06-08 11:05:33 +02:00
const auto method = req.method();
switch (method)
{
case http::verb::post: // fall through
case http::verb::get:
{
2020-06-08 18:48:27 +02:00
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);
2020-06-08 19:27:40 +02:00
}
catch(...){
deliver_status(socket, req, http::status::internal_server_error);
2020-06-08 23:12:05 +02:00
}
}
else {
if(method == http::verb::get && !_doc_root.empty())
{
deliver_file(socket, req);
}else{
deliver_status(socket, req, http::status::not_found);
}
2020-06-08 23:12:05 +02:00
}
break;
2020-06-08 23:12:05 +02:00
}
2020-06-08 18:48:27 +02:00
default:
2020-06-08 19:24:57 +02:00
deliver_status(socket, req, http::status::method_not_allowed);
2020-06-08 18:48:27 +02:00
};
2020-06-08 19:24:57 +02:00
if (ec)
break;
2020-06-08 17:02:48 +02:00
}
2020-06-08 11:05:33 +02:00
2020-06-22 13:33:47 +02:00
the_end:
2020-06-08 17:02:48 +02:00
socket->shutdown(tcp::socket::shutdown_send, ec);
delete socket;
2020-06-08 11:05:33 +02:00
}
2020-06-19 22:11:04 +02:00
void Webserver::runner(Webserver *me)
2020-06-08 11:05:33 +02:00
{
2020-06-19 22:11:04 +02:00
while (me->_running)
2020-06-08 11:05:33 +02:00
{
2020-06-19 22:11:04 +02:00
tcp::socket* socket = new tcp::socket{me->_ioc};
me->_acceptor.accept(*socket);
2020-06-08 11:05:33 +02:00
std::function< void() > tf = [=]()
{
2020-06-19 22:11:04 +02:00
me->thread_init();
me->do_session(socket);
me->thread_done();
};
2020-06-08 13:01:56 +02:00
std::thread{tf}.detach();
2020-06-08 11:05:33 +02:00
}
}
2020-06-19 22:11:04 +02:00
void Webserver::run()
{
_running = true;
_runner = std::thread(runner, this);
}
2020-06-08 17:02:48 +02:00
void Webserver::shutdown()
{
_running = false;
}
2020-06-08 11:05:33 +02:00
};