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 {
|
2020-06-11 23:24:05 +02:00
|
|
|
Webserver::Webserver(net::ip::address addr, unsigned short port, const std::string& doc_root)
|
2020-06-12 10:11:06 +02:00
|
|
|
: _ioc{1}
|
2020-06-23 23:38:49 +02:00
|
|
|
, _acceptor{_ioc, {addr, port}, false}
|
2020-06-12 10:11:06 +02:00
|
|
|
, _doc_root{doc_root}
|
|
|
|
, _generic_handler{}
|
2020-06-17 10:09:25 +02:00
|
|
|
, _port{port}
|
2020-06-12 10:11:06 +02:00
|
|
|
, _running{false}
|
2020-06-11 23:24:05 +02:00
|
|
|
{ }
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-06-11 23:24:05 +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);
|
2020-06-12 10:11:06 +02:00
|
|
|
_urls.emplace(url_regex, Handling{boost::regex(url_regex), handler});
|
2020-06-08 11:05:33 +02:00
|
|
|
}
|
|
|
|
|
2020-06-11 23:24:05 +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);
|
|
|
|
}
|
|
|
|
|
2020-06-11 23:24:05 +02:00
|
|
|
|
2020-06-12 10:11:06 +02:00
|
|
|
void Webserver::add_generic_url_handler(handler_t handler)
|
|
|
|
{
|
|
|
|
_generic_handler = handler;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Webserver::remove_generic_url_handler()
|
|
|
|
{
|
|
|
|
_generic_handler = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-12 11:13:57 +02:00
|
|
|
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;
|
2020-06-11 23:24:05 +02:00
|
|
|
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());
|
2020-06-12 11:13:57 +02:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2020-06-17 16:35:26 +02:00
|
|
|
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;
|
|
|
|
}
|
2020-06-12 11:13:57 +02:00
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-06-11 23:24:05 +02:00
|
|
|
|
2020-06-12 10:57:14 +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());
|
2020-11-12 13:20:29 +01:00
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-06-11 23:24:05 +02:00
|
|
|
|
2020-06-12 10:57:14 +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
|
|
|
|
2020-06-12 10:11:06 +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-12 10:11:06 +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) {
|
2020-10-09 10:52:45 +02:00
|
|
|
#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
|
|
|
|
2020-06-12 10:11:06 +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) {
|
2020-06-12 11:32:47 +02:00
|
|
|
try{
|
|
|
|
const Webserver::response res = handler(m, req);
|
|
|
|
http::write(*socket, res, ec);
|
2020-06-08 19:27:40 +02:00
|
|
|
}
|
2020-06-12 11:32:47 +02:00
|
|
|
catch(...){
|
|
|
|
deliver_status(socket, req, http::status::internal_server_error);
|
2020-06-08 23:12:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2020-06-12 10:11:06 +02:00
|
|
|
if(method == http::verb::get && !_doc_root.empty())
|
2020-06-11 23:24:05 +02:00
|
|
|
{
|
|
|
|
deliver_file(socket, req);
|
|
|
|
}else{
|
|
|
|
deliver_status(socket, req, http::status::not_found);
|
|
|
|
}
|
2020-06-08 23:12:05 +02:00
|
|
|
}
|
2020-06-12 10:11:06 +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
|
|
|
|
2020-06-18 16:05:21 +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-11 23:24:05 +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
|
|
|
|
2020-06-11 23:24:05 +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-11 23:24:05 +02:00
|
|
|
};
|
|
|
|
|
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
|
|
|
};
|
|
|
|
|