197 lines
5.9 KiB
C++
197 lines
5.9 KiB
C++
#include "server.hpp"
|
|
#include <boost/asio/detail/chrono.hpp>
|
|
#include <iostream>
|
|
#include <chrono>
|
|
|
|
HttpSession::HttpSession(net::ip::tcp::socket&& socket,
|
|
std::shared_ptr<Database> db,
|
|
std::shared_ptr<std::atomic<std::size_t>> sessionCounter)
|
|
: stream(std::move(socket)), db(db), sessionCounter(sessionCounter) {
|
|
if (sessionCounter) {
|
|
sessionCounter->fetch_add(1);
|
|
}
|
|
}
|
|
|
|
HttpSession::~HttpSession() {
|
|
if (sessionCounter) {
|
|
sessionCounter->fetch_sub(1);
|
|
}
|
|
}
|
|
|
|
void HttpSession::run() {
|
|
readRequest();
|
|
}
|
|
|
|
void HttpSession::readRequest() {
|
|
req = {};
|
|
stream.expires_after(net::chrono::seconds(15));
|
|
http::async_read(
|
|
stream,
|
|
buffer,
|
|
req,
|
|
beast::bind_front_handler(
|
|
&HttpSession::onRead,
|
|
shared_from_this()
|
|
)
|
|
);
|
|
}
|
|
|
|
void HttpSession::onRead(beast::error_code ec, std::size_t) {
|
|
if (ec) {
|
|
handleError(ec, "read");
|
|
return;
|
|
}
|
|
processRequest(std::move(req));
|
|
}
|
|
|
|
void HttpSession::processRequest(http::request<http::string_body>&& request) {
|
|
auto self = shared_from_this();
|
|
|
|
auto sendJson = [self](http::status status, unsigned version, json body) {
|
|
http::response<http::string_body> res{status, version};
|
|
res.set(http::field::server, "Cards Server");
|
|
res.set(http::field::content_type, "application/json");
|
|
res.body() = body.dump(4);
|
|
res.prepare_payload();
|
|
self->writeResponse(std::move(res));
|
|
};
|
|
|
|
if (request.method() == http::verb::get && request.target() == "/") {
|
|
db->getRandomRecord([self, sendJson, version = request.version()](json body) {
|
|
net::post(
|
|
self->stream.get_executor(),
|
|
[self, sendJson, version, body = std::move(body)]() mutable {
|
|
sendJson(http::status::ok, version, std::move(body));
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (request.method() == http::verb::get && request.target().starts_with("/cards")) {
|
|
int count = 0;
|
|
try {
|
|
std::string number{request.target()};
|
|
auto pos = number.find("?count=");
|
|
if (pos == std::string::npos) {
|
|
throw std::invalid_argument("missing count");
|
|
}
|
|
size_t start = pos + 7;
|
|
size_t end = number.find('?', start);
|
|
if (end == std::string::npos) {
|
|
end = number.length();
|
|
}
|
|
count = std::stoi(number.substr(start, end - start));
|
|
count = std::max(1, std::min(count, 1000));
|
|
} catch (const std::exception&) {
|
|
sendJson(http::status::bad_request, request.version(),
|
|
json{{"error", "Invalid count parameter"}});
|
|
return;
|
|
}
|
|
db->getRandomRecords(count, 10, 1000,
|
|
[self, sendJson, version = request.version()](json body) {
|
|
net::post(
|
|
self->stream.get_executor(),
|
|
[self, sendJson, version, body = std::move(body)]() mutable {
|
|
sendJson(http::status::ok, version, std::move(body));
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (request.method() == http::verb::get && request.target() == "/health") {
|
|
sendJson(http::status::ok, request.version(), json{
|
|
{"status", "ok"},
|
|
{"timestamp", std::chrono::system_clock::now().time_since_epoch().count()}
|
|
});
|
|
return;
|
|
}
|
|
|
|
sendJson(http::status::not_found, request.version(), json{
|
|
{"error", "Not Found"}
|
|
});
|
|
}
|
|
|
|
void HttpSession::writeResponse(http::response<http::string_body> res) {
|
|
auto self = shared_from_this();
|
|
auto response = std::make_shared<http::response<http::string_body>>(std::move(res));
|
|
http::async_write(
|
|
stream,
|
|
*response,
|
|
[self, response](beast::error_code ec, std::size_t) {
|
|
if (ec) {
|
|
self->handleError(ec, "write");
|
|
}
|
|
beast::error_code ignored;
|
|
self->stream.socket().shutdown(net::ip::tcp::socket::shutdown_send, ignored);
|
|
}
|
|
);
|
|
}
|
|
|
|
void HttpSession::handleError(beast::error_code ec, const char* what) {
|
|
if (ec == net::error::eof || ec == net::error::connection_reset) {
|
|
// Normal disconnection
|
|
return;
|
|
}
|
|
std::cerr << "Error in " << what << ": " << ec.message() << std::endl;
|
|
}
|
|
|
|
// Implementation of Server
|
|
Server::Server(const std::string& address, int port, int threads, std::shared_ptr<Database> db)
|
|
: ioc(threads),
|
|
acceptor(ioc, net::ip::tcp::endpoint(net::ip::make_address(address), port), true),
|
|
db(db),
|
|
threads(threads),
|
|
acceptTimer(ioc),
|
|
activeSessions(std::make_shared<std::atomic<std::size_t>>(0)) {}
|
|
|
|
void Server::run() {
|
|
accept();
|
|
|
|
// Run the I/O service on the requested number of threads
|
|
threadPool.reserve(threads);
|
|
for (int i = 0; i < threads; ++i) {
|
|
threadPool.emplace_back([this]() {
|
|
ioc.run();
|
|
});
|
|
}
|
|
|
|
// Wait for all threads to finish
|
|
for (auto& t : threadPool) {
|
|
if (t.joinable()) {
|
|
t.join();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Server::stop() {
|
|
ioc.stop();
|
|
}
|
|
|
|
void Server::accept() {
|
|
acceptor.async_accept(
|
|
net::make_strand(ioc),
|
|
beast::bind_front_handler(&Server::onAccept, this)
|
|
);
|
|
}
|
|
|
|
void Server::onAccept(beast::error_code ec, net::ip::tcp::socket socket) {
|
|
if (ec) {
|
|
std::cerr << "Accept error: " << ec.message() << std::endl;
|
|
if (!ioc.stopped()) {
|
|
acceptTimer.expires_after(std::chrono::milliseconds(100));
|
|
acceptTimer.async_wait([this](beast::error_code ec) {
|
|
if (!ioc.stopped()) { accept(); }
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (activeSessions->load() >= maxSessions) {
|
|
beast::error_code ignore;
|
|
socket.close(ignore);
|
|
} else {
|
|
std::make_shared<HttpSession>(std::move(socket), db, activeSessions)->run();
|
|
}
|
|
accept();
|
|
}
|