858 lines
25 KiB
C++
858 lines
25 KiB
C++
#include "CCServer.h"
|
|
|
|
#include "CryptoUtils.h"
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Based on Beast example: Advanced server
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
#include <boost/beast/core.hpp>
|
|
#include <boost/beast/http.hpp>
|
|
#include <boost/beast/version.hpp>
|
|
#include <boost/asio/bind_executor.hpp>
|
|
#include <boost/asio/dispatch.hpp>
|
|
#include <boost/asio/signal_set.hpp>
|
|
#include <boost/asio/strand.hpp>
|
|
#include <boost/crc.hpp>
|
|
#include <boost/iostreams/filtering_stream.hpp>
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/signals2.hpp>
|
|
#include <boost/tokenizer.hpp>
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <shared_mutex>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace fs = boost::filesystem; // from <boost/filesystem.hpp>
|
|
|
|
namespace beast = boost::beast; // from <boost/beast.hpp>
|
|
namespace http = beast::http; // from <boost/beast/http.hpp>
|
|
namespace net = boost::asio; // from <boost/asio.hpp>
|
|
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
|
|
|
static constexpr auto APIS_UINT8_MIN = std::numeric_limits<uint8_t>::min();
|
|
static constexpr auto APIS_UINT8_MAX = std::numeric_limits<uint8_t>::max();
|
|
|
|
#define MAXHESLO 225
|
|
#define MAX_DEKODHESLO (MAXHESLO-3)
|
|
|
|
#define KodcharToChar(znak2,poloha,posun) static_cast<uint8_t>(-(password[poloha])+(~(znak2))-(posun))
|
|
|
|
namespace
|
|
{
|
|
|
|
static const std::string cryptFileHeader("##CAPISF## Copyright (c) 2003 APIS spol. s r.o. SLOVAKIA~");
|
|
//static const std::string passReal("flckdscwjregfvfgbujvbnqajmadmgfjmhfmghplgghesrrhsufservvysfidgfysdnmsybsaftoprecofoplyrmnfsvcapridssedalenttrmfusposvresalbostreqnnapswolnfrdweneplmnzcavsfgusqootrpqaressnvclpacfdrgesklojphsdqtyczvxcbfhtryntinokusgkhjgchtx");
|
|
static const uint8_t passHidden2[] = { 0x89, 0xdb, 0x2c, 0x79, 0xd6, 0xa5, 0x7a, 0x7b, 0x6d, 0xae, 0x67, 0xee, 0xb2, 0x9a, 0x2c, 0xbe, 0xb7, 0xac, 0x6a, 0x56, 0xe8, 0xb2, 0xda, 0xde, 0xaa, 0x79, 0xda, 0xa6, 0xcc, 0x28, 0x96, 0x77, 0xeb, 0x77, 0x07, 0xa7, 0x7a, 0x99, 0x66, 0x9f, 0x37, 0x1a, 0xbe, 0xc7, 0xe0, 0xba, 0xca, 0xa8, 0xa2, 0xda, 0xe9, 0xa9, 0xaa, 0xde, 0xb2, 0xc9, 0xef, 0x72, 0x5a, 0x5a, 0x71, 0xf7, 0x6b, 0x81, 0xeb, 0x24, 0x96, 0x88, 0xe9, 0x86, 0xc7, 0x6a, 0xb7, 0x27, 0x33, 0xbf, 0x17, 0x1b, 0x7e, 0x1b, 0x6b, 0xca, 0x7b, 0x62, 0x9e, 0x89, 0x2e, 0xb2, 0x09, 0x21, 0x8e, 0x07, 0x21, 0xb7, 0x10, 0x34 };
|
|
|
|
class apis_read_filter
|
|
{
|
|
public:
|
|
typedef char char_type;
|
|
explicit apis_read_filter(size_t initialPosition)
|
|
: m_position(initialPosition)
|
|
{ }
|
|
|
|
struct category
|
|
: boost::iostreams::input
|
|
, boost::iostreams::filter_tag
|
|
, boost::iostreams::multichar_tag
|
|
, boost::iostreams::optimally_buffered_tag
|
|
{};
|
|
|
|
std::streamsize optimal_buffer_size() const { return 0; }
|
|
|
|
template<typename Source>
|
|
std::streamsize read(Source& src, char_type* s, std::streamsize n)
|
|
{
|
|
std::streamsize result = boost::iostreams::read(src, s, n);
|
|
if (result == -1)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
int dwPacketPos = ((m_position / MAX_DEKODHESLO) % 29) + 2;
|
|
int iPoloha = m_position % MAX_DEKODHESLO;
|
|
|
|
m_position += static_cast<size_t>(result);
|
|
|
|
for (int i = 0; i < result; i++)
|
|
{
|
|
if (iPoloha == MAX_DEKODHESLO)
|
|
{
|
|
iPoloha = 0;
|
|
dwPacketPos++;
|
|
}
|
|
if (dwPacketPos >= 31)
|
|
{
|
|
dwPacketPos = 2;
|
|
}
|
|
|
|
static const uint8_t passHidden[] = { 0x7e, 0x57, 0x24, 0x76, 0xc7, 0x30, 0x8e, 0xb7, 0xa0, 0x7e, 0xf7, 0xe0, 0x6e, 0xe8, 0xef, 0x6e, 0x7a, 0x9a, 0x8e, 0x66, 0x9d, 0x9a, 0x07, 0xe3, 0x9a, 0x17, 0xe6, 0x82, 0x1a, 0x65, 0x82, 0x08, 0x5e, 0xb2, 0xba, 0xe1, 0xb2, 0xe7, 0xec, 0x7a, 0xbb, 0xef, 0xca, 0xc7, 0xe2, 0x76, 0x07, 0xf2, 0xb1, 0xd9, 0xe6, 0xb3, 0x26, 0xec, 0x69, 0xfb, 0x68, 0xa6, 0xb7, 0x9c, 0xa1, 0xfa, 0x29, 0x97, 0x2a, 0xe6, 0x9d, 0xfb, 0x2f, 0x71, 0xaa, 0x6b };
|
|
if (iPoloha < sizeof(passHidden) * 4 / 3)
|
|
{
|
|
static const auto password = base64Encode(passHidden, sizeof(passHidden));
|
|
s[i] = KodcharToChar(s[i], iPoloha, iPoloha + dwPacketPos);
|
|
}
|
|
else
|
|
{
|
|
static const auto password = base64Encode(passHidden2, sizeof(passHidden2));
|
|
s[i] = KodcharToChar(s[i], iPoloha - sizeof(passHidden) * 4 / 3, iPoloha + dwPacketPos);
|
|
}
|
|
++iPoloha;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
private:
|
|
size_t m_position;
|
|
};
|
|
|
|
void decryptBuffer(std::vector<uint8_t>& buffer)
|
|
{
|
|
bool encrypted = false;
|
|
if (buffer.size() >= cryptFileHeader.size())
|
|
{
|
|
encrypted = std::equal(cryptFileHeader.begin(), cryptFileHeader.end(), buffer.begin());
|
|
}
|
|
if (!encrypted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
boost::iostreams::array_source src{ (const char*)buffer.data() + cryptFileHeader.size(), buffer.size() - cryptFileHeader.size()};
|
|
boost::iostreams::filtering_istream is;
|
|
is.push(apis_read_filter({ cryptFileHeader.size() }));
|
|
is.push(src);
|
|
|
|
std::vector<uint8_t> decrypted(buffer.size() - cryptFileHeader.size());
|
|
is.read((char*)decrypted.data(), decrypted.size());
|
|
if (decrypted.size() > sizeof(uint32_t))
|
|
{
|
|
auto headerLen = reinterpret_cast<uint32_t*>(decrypted.data())[0];
|
|
if (decrypted.size() > headerLen)
|
|
{
|
|
decrypted.erase(decrypted.begin(), decrypted.begin() + headerLen);
|
|
}
|
|
}
|
|
buffer = decrypted;
|
|
}
|
|
|
|
class etag_cache
|
|
{
|
|
public:
|
|
void insert(boost::beast::string_view path, boost::beast::string_view etag)
|
|
{
|
|
std::unique_lock<std::shared_mutex> lock(mutex_);
|
|
map_.emplace(path, etag);
|
|
}
|
|
|
|
bool check_path(boost::beast::string_view path, boost::beast::string_view etag) const
|
|
{
|
|
std::shared_lock<std::shared_mutex> lock(mutex_);
|
|
auto it = map_.find(static_cast<std::string>(path));
|
|
if (it != map_.end() && it->second == etag)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
std::unique_lock<std::shared_mutex> lock(mutex_);
|
|
map_.clear();
|
|
}
|
|
|
|
private:
|
|
mutable std::shared_mutex mutex_;
|
|
std::unordered_map<std::string, std::string> map_;
|
|
};
|
|
|
|
// Return a reasonable mime type based on the extension of a file.
|
|
beast::string_view
|
|
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";
|
|
if (iequals(ext, ".html")) return "text/html";
|
|
if (iequals(ext, ".php")) return "text/html";
|
|
if (iequals(ext, ".css")) return "text/css";
|
|
if (iequals(ext, ".txt")) return "text/plain";
|
|
if (iequals(ext, ".js")) return "application/javascript";
|
|
if (iequals(ext, ".json")) return "application/json";
|
|
if (iequals(ext, ".xml")) return "application/xml";
|
|
if (iequals(ext, ".swf")) return "application/x-shockwave-flash";
|
|
if (iequals(ext, ".flv")) return "video/x-flv";
|
|
if (iequals(ext, ".png")) return "image/png";
|
|
if (iequals(ext, ".jpe")) return "image/jpeg";
|
|
if (iequals(ext, ".jpeg")) return "image/jpeg";
|
|
if (iequals(ext, ".jpg")) return "image/jpeg";
|
|
if (iequals(ext, ".gif")) return "image/gif";
|
|
if (iequals(ext, ".bmp")) return "image/bmp";
|
|
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";
|
|
if (iequals(ext, ".svgz")) return "image/svg+xml";
|
|
return "application/text";
|
|
}
|
|
|
|
// Append an HTTP rel-path to a local filesystem path.
|
|
// The returned path is normalized for the platform.
|
|
std::string
|
|
path_cat(
|
|
beast::string_view base,
|
|
beast::string_view path)
|
|
{
|
|
if (base.empty())
|
|
return std::string(path);
|
|
std::string result(base);
|
|
#ifdef BOOST_MSVC
|
|
char constexpr path_separator = '\\';
|
|
if (result.back() == path_separator)
|
|
result.resize(result.size() - 1);
|
|
result.append(path.data(), path.size());
|
|
for (auto& c : result)
|
|
if (c == '/')
|
|
c = path_separator;
|
|
#else
|
|
char constexpr path_separator = '/';
|
|
if (result.back() == path_separator)
|
|
result.resize(result.size() - 1);
|
|
result.append(path.data(), path.size());
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
// This function produces an HTTP response for the given
|
|
// request. The type of the response object depends on the
|
|
// contents of the request, so the interface requires the
|
|
// caller to pass a generic lambda for receiving the response.
|
|
template<
|
|
class Body, class Allocator,
|
|
class Send>
|
|
void
|
|
handle_request(
|
|
beast::string_view doc_root,
|
|
etag_cache& cache,
|
|
http::request<Body, http::basic_fields<Allocator>>&& req,
|
|
Send&& send)
|
|
{
|
|
// Returns a bad request response
|
|
auto const bad_request =
|
|
[&req](beast::string_view why)
|
|
{
|
|
http::response<http::string_body> res{ http::status::bad_request, req.version() };
|
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
res.set(http::field::content_type, "text/html");
|
|
res.keep_alive(req.keep_alive());
|
|
res.body() = std::string(why);
|
|
res.prepare_payload();
|
|
return res;
|
|
};
|
|
|
|
// Returns a not found response
|
|
auto const not_found =
|
|
[&req](beast::string_view target)
|
|
{
|
|
http::response<http::string_body> res{ http::status::not_found, req.version() };
|
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
res.set(http::field::content_type, "text/html");
|
|
res.keep_alive(req.keep_alive());
|
|
res.body() = "The resource '" + std::string(target) + "' was not found.";
|
|
res.prepare_payload();
|
|
return res;
|
|
};
|
|
|
|
// Returns a server error response
|
|
auto const server_error =
|
|
[&req](beast::string_view what)
|
|
{
|
|
http::response<http::string_body> res{ http::status::internal_server_error, req.version() };
|
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
res.set(http::field::content_type, "text/html");
|
|
res.keep_alive(req.keep_alive());
|
|
res.body() = "An error occurred: '" + std::string(what) + "'";
|
|
res.prepare_payload();
|
|
return res;
|
|
};
|
|
|
|
// Returns a not modified response
|
|
auto const not_modified =
|
|
[&req](boost::beast::string_view etag)
|
|
{
|
|
http::response<http::empty_body> res{ http::status::not_modified, req.version() };
|
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
res.set(http::field::etag, etag);
|
|
res.keep_alive(req.keep_alive());
|
|
res.prepare_payload();
|
|
return res;
|
|
};
|
|
|
|
// Make sure we can handle the method
|
|
if (req.method() != http::verb::get &&
|
|
req.method() != http::verb::head)
|
|
return send(bad_request("Unknown HTTP-method"));
|
|
|
|
// Request path must be absolute and not contain "..".
|
|
if (req.target().empty() ||
|
|
req.target()[0] != '/' ||
|
|
req.target().find("..") != beast::string_view::npos)
|
|
return send(bad_request("Illegal request-target"));
|
|
|
|
auto it = req.find(http::field::if_none_match);
|
|
if (it != req.end())
|
|
{
|
|
auto etags = std::string(it->value());
|
|
|
|
typedef boost::tokenizer<boost::char_separator<char>> Tokenizer;
|
|
boost::char_separator<char> sep(", ");
|
|
Tokenizer tok{ etags, sep };
|
|
for (Tokenizer::iterator tokIt = tok.begin(); tokIt != tok.end(); ++tokIt)
|
|
{
|
|
std::string etag(*tokIt);
|
|
boost::string_view checksum(etag);
|
|
if (checksum.front() == '"')
|
|
{
|
|
checksum.remove_prefix(1);
|
|
checksum.remove_suffix(1);
|
|
}
|
|
|
|
if (cache.check_path(req.target(), checksum))
|
|
{
|
|
return send(not_modified(etag));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build the path to the requested file
|
|
beast::string_view targetPath = req.target();
|
|
auto pos = targetPath.find_first_of('?');
|
|
if (pos != std::string::npos)
|
|
{
|
|
targetPath = targetPath.substr(0, pos);
|
|
}
|
|
std::string path = path_cat(doc_root, targetPath);
|
|
if (targetPath.back() == '/')
|
|
path.append("index.htm");
|
|
|
|
// Attempt to open the file
|
|
if (!boost::filesystem::is_regular_file(path))
|
|
{
|
|
return send(not_found(req.target()));
|
|
}
|
|
|
|
std::ifstream body(path, std::ifstream::binary);
|
|
|
|
body.seekg(0, std::ios::end);
|
|
auto size = static_cast<size_t>(body.tellg());
|
|
std::vector<uint8_t> buffer(size, 0);
|
|
body.seekg(0);
|
|
body.read((char*)buffer.data(), size);
|
|
body.close();
|
|
|
|
decryptBuffer(buffer);
|
|
size = buffer.size();
|
|
|
|
boost::crc_32_type result;
|
|
result.process_bytes(buffer.data(), buffer.size());
|
|
std::ostringstream checksum;
|
|
checksum << std::hex << result.checksum();
|
|
|
|
auto etag = std::string("\"") + checksum.str() + "\"";
|
|
cache.insert(req.target(), checksum.str());
|
|
|
|
// Respond to HEAD request
|
|
if (req.method() == http::verb::head)
|
|
{
|
|
http::response<http::empty_body> res{ http::status::ok, req.version() };
|
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
res.set(http::field::content_type, mime_type(path));
|
|
res.content_length(size);
|
|
res.keep_alive(req.keep_alive());
|
|
return send(std::move(res));
|
|
}
|
|
|
|
// Respond to GET request
|
|
// TODO: replace this by decryption
|
|
http::response<http::buffer_body> res{ http::status::ok, req.version() };
|
|
res.set(http::field::server, "Based on " BOOST_BEAST_VERSION_STRING);
|
|
res.set(http::field::content_type, mime_type(path));
|
|
res.set(http::field::etag, etag);
|
|
res.body().data = buffer.data();
|
|
res.body().size = buffer.size();
|
|
res.body().more = false;
|
|
res.content_length(size);
|
|
res.keep_alive(req.keep_alive());
|
|
return send(std::move(res));
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Report a failure
|
|
void
|
|
fail(beast::error_code ec, char const* what)
|
|
{
|
|
std::cerr << what << ": " << ec.message() << "\n";
|
|
}
|
|
|
|
|
|
// Handles an HTTP server connection
|
|
class http_session : public std::enable_shared_from_this<http_session>
|
|
{
|
|
// This queue is used for HTTP pipelining.
|
|
class queue
|
|
{
|
|
enum
|
|
{
|
|
// Maximum number of responses we will queue
|
|
limit = 8
|
|
};
|
|
|
|
// The type-erased, saved work item
|
|
struct work
|
|
{
|
|
virtual ~work() = default;
|
|
virtual void operator()() = 0;
|
|
};
|
|
|
|
http_session& self_;
|
|
std::vector<std::unique_ptr<work>> items_;
|
|
|
|
public:
|
|
explicit
|
|
queue(http_session& self)
|
|
: self_(self)
|
|
{
|
|
static_assert(limit > 0, "queue limit must be positive");
|
|
items_.reserve(limit);
|
|
}
|
|
|
|
// Returns `true` if we have reached the queue limit
|
|
bool
|
|
is_full() const
|
|
{
|
|
return items_.size() >= limit;
|
|
}
|
|
|
|
// Called when a message finishes sending
|
|
// Returns `true` if the caller should initiate a read
|
|
bool
|
|
on_write()
|
|
{
|
|
BOOST_ASSERT(!items_.empty());
|
|
auto const was_full = is_full();
|
|
items_.erase(items_.begin());
|
|
if (!items_.empty())
|
|
(*items_.front())();
|
|
return was_full;
|
|
}
|
|
|
|
// Called by the HTTP handler to send a response.
|
|
template<bool isRequest, class Body, class Fields>
|
|
void
|
|
operator()(http::message<isRequest, Body, Fields>&& msg)
|
|
{
|
|
// This holds a work item
|
|
struct work_impl : work
|
|
{
|
|
http_session& self_;
|
|
http::message<isRequest, Body, Fields> msg_;
|
|
|
|
work_impl(
|
|
http_session& self,
|
|
http::message<isRequest, Body, Fields>&& msg)
|
|
: self_(self)
|
|
, msg_(std::move(msg))
|
|
{
|
|
}
|
|
|
|
void
|
|
operator()()
|
|
{
|
|
http::async_write(
|
|
self_.stream_,
|
|
msg_,
|
|
beast::bind_front_handler(
|
|
&http_session::on_write,
|
|
self_.shared_from_this(),
|
|
msg_.need_eof()));
|
|
}
|
|
};
|
|
|
|
// Allocate and store the work
|
|
items_.push_back(
|
|
boost::make_unique<work_impl>(self_, std::move(msg)));
|
|
|
|
// If there was no previous work, start this one
|
|
if (items_.size() == 1)
|
|
(*items_.front())();
|
|
}
|
|
};
|
|
|
|
beast::tcp_stream stream_;
|
|
beast::flat_buffer buffer_;
|
|
std::shared_ptr<std::string const> doc_root_;
|
|
etag_cache& etag_cache_;
|
|
queue queue_;
|
|
|
|
// The parser is stored in an optional container so we can
|
|
// construct it from scratch it at the beginning of each new message.
|
|
boost::optional<http::request_parser<http::string_body>> parser_;
|
|
|
|
public:
|
|
// Take ownership of the socket
|
|
http_session(
|
|
tcp::socket&& socket,
|
|
std::shared_ptr<std::string const> const& doc_root,
|
|
etag_cache& etag_cache)
|
|
: stream_(std::move(socket))
|
|
, doc_root_(doc_root)
|
|
, etag_cache_(etag_cache)
|
|
, queue_(*this)
|
|
{
|
|
}
|
|
|
|
// Start the session
|
|
void
|
|
run()
|
|
{
|
|
// We need to be executing within a strand to perform async operations
|
|
// on the I/O objects in this session. Although not strictly necessary
|
|
// for single-threaded contexts, this example code is written to be
|
|
// thread-safe by default.
|
|
net::dispatch(
|
|
stream_.get_executor(),
|
|
beast::bind_front_handler(
|
|
&http_session::do_read,
|
|
this->shared_from_this()));
|
|
}
|
|
|
|
|
|
private:
|
|
void
|
|
do_read()
|
|
{
|
|
// Construct a new parser for each message
|
|
parser_.emplace();
|
|
|
|
// Apply a reasonable limit to the allowed size
|
|
// of the body in bytes to prevent abuse.
|
|
parser_->body_limit(10000);
|
|
|
|
// Set the timeout.
|
|
stream_.expires_after(std::chrono::seconds(30));
|
|
|
|
// Read a request using the parser-oriented interface
|
|
http::async_read(
|
|
stream_,
|
|
buffer_,
|
|
*parser_,
|
|
beast::bind_front_handler(
|
|
&http_session::on_read,
|
|
shared_from_this()));
|
|
}
|
|
|
|
void
|
|
on_read(beast::error_code ec, std::size_t bytes_transferred)
|
|
{
|
|
boost::ignore_unused(bytes_transferred);
|
|
|
|
if (ec == beast::error::timeout)
|
|
return;
|
|
|
|
// This means they closed the connection
|
|
if (ec == http::error::end_of_stream)
|
|
return do_close();
|
|
|
|
if (ec)
|
|
return fail(ec, "read");
|
|
|
|
// Send the response
|
|
handle_request(*doc_root_, etag_cache_, parser_->release(), queue_);
|
|
|
|
// If we aren't at the queue limit, try to pipeline another request
|
|
if (!queue_.is_full())
|
|
do_read();
|
|
}
|
|
|
|
void
|
|
on_write(bool close, beast::error_code ec, std::size_t bytes_transferred)
|
|
{
|
|
boost::ignore_unused(bytes_transferred);
|
|
|
|
if (ec == beast::error::timeout)
|
|
return;
|
|
|
|
if (ec)
|
|
return fail(ec, "write");
|
|
|
|
if (close)
|
|
{
|
|
// This means we should close the connection, usually because
|
|
// the response indicated the "Connection: close" semantic.
|
|
return do_close();
|
|
}
|
|
|
|
// Inform the queue that a write completed
|
|
if (queue_.on_write())
|
|
{
|
|
// Read another request
|
|
do_read();
|
|
}
|
|
}
|
|
|
|
void
|
|
do_close()
|
|
{
|
|
// Send a TCP shutdown
|
|
beast::error_code ec;
|
|
stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
|
|
|
|
// At this point the connection is closed gracefully
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Accepts incoming connections and launches the sessions
|
|
class listener : public std::enable_shared_from_this<listener>
|
|
{
|
|
net::io_context& ioc_;
|
|
tcp::acceptor acceptor_;
|
|
std::shared_ptr<std::string const> doc_root_;
|
|
etag_cache& etag_cache_;
|
|
|
|
public:
|
|
listener(
|
|
net::io_context& ioc,
|
|
tcp::endpoint endpoint,
|
|
std::shared_ptr<std::string const> const& doc_root,
|
|
etag_cache& etagCache)
|
|
: ioc_(ioc)
|
|
, acceptor_(net::make_strand(ioc))
|
|
, doc_root_(doc_root)
|
|
, etag_cache_(etagCache)
|
|
{
|
|
beast::error_code ec;
|
|
|
|
// Open the acceptor
|
|
acceptor_.open(endpoint.protocol(), ec);
|
|
if (ec)
|
|
{
|
|
fail(ec, "open");
|
|
return;
|
|
}
|
|
|
|
// // Allow address reuse
|
|
// acceptor_.set_option(net::socket_base::reuse_address(true), ec);
|
|
// if (ec)
|
|
// {
|
|
// fail(ec, "set_option");
|
|
// return;
|
|
// }
|
|
|
|
// Bind to the server address
|
|
acceptor_.bind(endpoint, ec);
|
|
if (ec)
|
|
{
|
|
fail(ec, "bind");
|
|
return;
|
|
}
|
|
|
|
// Start listening for connections
|
|
acceptor_.listen(
|
|
net::socket_base::max_listen_connections, ec);
|
|
if (ec)
|
|
{
|
|
fail(ec, "listen");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Start accepting incoming connections
|
|
void
|
|
run()
|
|
{
|
|
// We need to be executing within a strand to perform async operations
|
|
// on the I/O objects in this session. Although not strictly necessary
|
|
// for single-threaded contexts, this example code is written to be
|
|
// thread-safe by default.
|
|
net::dispatch(
|
|
acceptor_.get_executor(),
|
|
beast::bind_front_handler(
|
|
&listener::do_accept,
|
|
this->shared_from_this()));
|
|
}
|
|
|
|
private:
|
|
void
|
|
do_accept()
|
|
{
|
|
// The new connection gets its own strand
|
|
acceptor_.async_accept(
|
|
net::make_strand(ioc_),
|
|
beast::bind_front_handler(
|
|
&listener::on_accept,
|
|
shared_from_this()));
|
|
}
|
|
|
|
void
|
|
on_accept(beast::error_code ec, tcp::socket socket)
|
|
{
|
|
if (ec)
|
|
{
|
|
fail(ec, "accept");
|
|
}
|
|
else
|
|
{
|
|
// Create the http session and run it
|
|
std::make_shared<http_session>(
|
|
std::move(socket),
|
|
doc_root_,
|
|
etag_cache_)->run();
|
|
}
|
|
|
|
// Accept another connection
|
|
do_accept();
|
|
}
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace detail
|
|
{
|
|
|
|
class CCServerImpl final
|
|
{
|
|
public:
|
|
CCServerImpl(const std::string& addressToListen, uint16_t portToListen, const std::string& docRoot, int threads)
|
|
: m_maxThreads{std::max(1, threads)}
|
|
, m_docRoot{ docRoot }
|
|
, m_etagCache{}
|
|
, m_endpoint{ boost::asio::ip::make_address(addressToListen), portToListen }
|
|
, m_ioc{ m_maxThreads }
|
|
, m_threads{}
|
|
{
|
|
}
|
|
|
|
~CCServerImpl()
|
|
{
|
|
shutdown();
|
|
}
|
|
|
|
public:
|
|
void run()
|
|
{
|
|
std::lock_guard<std::mutex> scopeGuard(m_mutex);
|
|
// Create and launch a listening port
|
|
std::make_shared<listener>(
|
|
m_ioc,
|
|
m_endpoint,
|
|
std::make_shared<std::string>(m_docRoot),
|
|
m_etagCache)->run();
|
|
|
|
// Run the I/O service on the requested number of threads
|
|
m_threads.reserve(m_maxThreads);
|
|
for (auto i = m_maxThreads - 1; i >= 0; --i)
|
|
{
|
|
m_threads.emplace_back([&]()
|
|
{
|
|
m_ioc.run();
|
|
if (i == 0)
|
|
{
|
|
m_onStopped();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void shutdown()
|
|
{
|
|
std::lock_guard<std::mutex> scopeGuard(m_mutex);
|
|
m_ioc.stop();
|
|
for (auto& t : m_threads)
|
|
{
|
|
t.join();
|
|
}
|
|
m_threads.clear();
|
|
}
|
|
|
|
bool isRunning() const
|
|
{
|
|
return m_ioc.stopped();
|
|
}
|
|
|
|
void clearCache()
|
|
{
|
|
m_etagCache.clear();
|
|
}
|
|
|
|
boost::signals2::connection connectOnStopped(const boost::signals2::signal<void()>::slot_type& slot)
|
|
{
|
|
return m_onStopped.connect(slot);
|
|
}
|
|
|
|
private:
|
|
mutable std::mutex m_mutex;
|
|
int m_maxThreads;
|
|
std::string m_docRoot;
|
|
etag_cache m_etagCache;
|
|
tcp::endpoint m_endpoint;
|
|
boost::asio::io_context m_ioc;
|
|
std::vector<std::thread> m_threads;
|
|
boost::signals2::signal<void()> m_onStopped;
|
|
};
|
|
|
|
}
|
|
|
|
CCServer::CCServer(const std::string& addressToListen, uint16_t portToListen, const std::string& docRoot, int threads)
|
|
: m_impl(std::make_unique<detail::CCServerImpl>(addressToListen, portToListen, docRoot, threads))
|
|
{}
|
|
|
|
|
|
CCServer::~CCServer()
|
|
{}
|
|
|
|
void CCServer::shutdown()
|
|
{
|
|
m_impl->shutdown();
|
|
}
|
|
|
|
void CCServer::clearCache()
|
|
{
|
|
m_impl->clearCache();
|
|
}
|
|
|
|
boost::signals2::connection CCServer::connectOnStopped(const boost::signals2::signal<void()>::slot_type & slot)
|
|
{
|
|
return m_impl->connectOnStopped(slot);
|
|
}
|
|
|
|
void CCServer::run()
|
|
{
|
|
m_impl->run();
|
|
}
|
|
|
|
bool CCServer::isRunning() const
|
|
{
|
|
return m_impl->isRunning();
|
|
}
|