Add project source files and build configuration
This commit is contained in:
parent
a7848f56dc
commit
99e0c2387a
|
|
@ -0,0 +1,76 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
project(cards)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Find required packages
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
# Find PostgreSQL (libpqxx)
|
||||
pkg_check_modules(PQXX REQUIRED libpqxx>=7.0.0)
|
||||
pkg_check_modules(PQ REQUIRED libpq)
|
||||
|
||||
# Find Boost
|
||||
find_package(Boost REQUIRED COMPONENTS system)
|
||||
|
||||
# Find nlohmann_json
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
|
||||
# Add executable
|
||||
add_executable(cards
|
||||
src/main.cpp
|
||||
src/database.cpp
|
||||
src/server.cpp
|
||||
)
|
||||
|
||||
# Include directories
|
||||
target_include_directories(cards PRIVATE
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${PQXX_INCLUDE_DIRS}
|
||||
${PQ_INCLUDE_DIRS}
|
||||
src
|
||||
)
|
||||
|
||||
# Link libraries
|
||||
target_link_libraries(cards
|
||||
${Boost_LIBRARIES}
|
||||
${PQXX_LIBRARIES}
|
||||
${PQ_LIBRARIES}
|
||||
Threads::Threads
|
||||
nlohmann_json::nlohmann_json
|
||||
)
|
||||
|
||||
# For Windows, add ws2_32 and crypt32
|
||||
if(WIN32)
|
||||
target_link_libraries(cards ws2_32 crypt32)
|
||||
endif()
|
||||
|
||||
# Compiler flags
|
||||
target_compile_definitions(cards PRIVATE BOOST_ALL_NO_LIB)
|
||||
|
||||
# Set properties
|
||||
set_target_properties(cards PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
)
|
||||
|
||||
# ─── Packaging (CPack) ────────────────────────────────────────────────
|
||||
set(CPACK_PACKAGE_NAME "cards")
|
||||
set(CPACK_PACKAGE_VERSION "1.0.0")
|
||||
set(CPACK_PACKAGE_VENDOR "cards")
|
||||
set(CPACK_PACKAGE_MAINTAINER "root")
|
||||
set(CPACK_PACKAGE_CONTACT "root@localhost")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Cards web server")
|
||||
set(CPACK_PACKAGE_DESCRIPTION "A simple C++20 web server for cards")
|
||||
set(CPACK_PACKAGE_SECTION "web")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION "web")
|
||||
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS
|
||||
"libpqxx-7.0 (>= 7.0), libpq5 (>= 13), libstdc++6 (>= 10), libgcc-s1"
|
||||
)
|
||||
set(CPACK_DEBIAN_FILE_NAME "cards-${CPACK_PACKAGE_VERSION}-Linux.deb")
|
||||
set(CPACK_GENERATOR "DEB")
|
||||
|
||||
include(CPack)
|
||||
|
||||
133
README.md
133
README.md
|
|
@ -1,2 +1,135 @@
|
|||
# cards
|
||||
|
||||
Веб-сервер на C++20 с использованием Boost.Beast и PostgreSQL. Отдаёт случайные записи из базы данных по REST API.
|
||||
|
||||
## Требования
|
||||
|
||||
- Компилятор с поддержкой C++20 (GCC 10+, Clang 10+)
|
||||
- CMake >= 3.14
|
||||
- PostgreSQL (runtime) + libpq + libpqxx >= 7.0
|
||||
- Boost (system)
|
||||
- nlohmann_json 3.x
|
||||
|
||||
Установка зависимостей (Debian/Ubuntu):
|
||||
|
||||
```bash
|
||||
sudo apt-get install libboost-all-dev libpq-dev libpqxx-dev nlohmann-json3-dev cmake build-essential
|
||||
```
|
||||
|
||||
## Сборка
|
||||
|
||||
```bash
|
||||
./configure.sh
|
||||
```
|
||||
|
||||
Или вручную:
|
||||
|
||||
```bash
|
||||
mkdir -p build && cd build
|
||||
cmake ..
|
||||
make -j"$(nproc)"
|
||||
cd ..
|
||||
```
|
||||
|
||||
Бинарник: `build/bin/cards`
|
||||
|
||||
## Конфигурация
|
||||
|
||||
По умолчанию читает `config.json` из рабочей директории. Можно указать путь к другому файлу:
|
||||
|
||||
```bash
|
||||
./build/bin/cards /path/to/config.json
|
||||
```
|
||||
|
||||
### Поля `config.json`
|
||||
|
||||
**server**
|
||||
| Поле | Тип | Значение по умолчанию |
|
||||
|---|---|---|
|
||||
| address | string | `"0.0.0.0"` |
|
||||
| port | int | `8080` |
|
||||
| threads | int | `4` |
|
||||
|
||||
**database**
|
||||
| Поле | Тип | Значение по умолчанию |
|
||||
|---|---|---|
|
||||
| host | string | `"localhost"` |
|
||||
| port | int | `5432` |
|
||||
| dbname | string | `"testdb"` |
|
||||
| user | string | `"postgres"` |
|
||||
| password | string | (пусто) |
|
||||
|
||||
### Переменные окружения
|
||||
|
||||
Переопределяют соответствующие поля `config.json`:
|
||||
|
||||
- `DB_HOST`
|
||||
- `DB_PORT`
|
||||
- `DB_NAME`
|
||||
- `DB_USER`
|
||||
- `DB_PASSWORD`
|
||||
|
||||
Пример:
|
||||
|
||||
```bash
|
||||
DB_PASSWORD=secret ./build/bin/cards
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Метод | Путь | Описание |
|
||||
|---|---|---|
|
||||
| GET | `/` | Случайная запись из таблицы `random_data` |
|
||||
| GET | `/health` | Проверка состояния сервера |
|
||||
|
||||
### Пример ответа `GET /`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Gamma",
|
||||
"value": 300,
|
||||
"description": "Third test record",
|
||||
"created_at": "2025-01-01 12:00:00",
|
||||
"timestamp": 1735732800000000000
|
||||
}
|
||||
```
|
||||
|
||||
### Пример ответа `GET /health`
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": 1735732800000000000
|
||||
}
|
||||
```
|
||||
|
||||
## База данных
|
||||
|
||||
При первом запуске автоматически создаётся таблица `random_data` и заполняется десятью тестовыми записями.
|
||||
|
||||
Схема таблицы:
|
||||
|
||||
```sql
|
||||
CREATE TABLE random_data (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
value INTEGER NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
## Сборка deb-пакета
|
||||
|
||||
```bash
|
||||
cd build
|
||||
cmake ..
|
||||
cpack
|
||||
```
|
||||
|
||||
Результат: `cards-1.0.0-Linux.deb`
|
||||
|
||||
## Завершение работы
|
||||
|
||||
Для остановки сервера отправьте сигнал `SIGINT` (Ctrl+C) или `SIGTERM`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"server": {
|
||||
"address": "0.0.0.0",
|
||||
"port": 8080,
|
||||
"threads": 4
|
||||
},
|
||||
"database": {
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"dbname": "testdb",
|
||||
"user": "postgres"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== Установка зависимостей ==="
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libboost-all-dev libpq-dev libpqxx-dev nlohmann-json3-dev cmake build-essential
|
||||
|
||||
echo "=== Сборка ==="
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake ..
|
||||
make -j"$(nproc)"
|
||||
cd ..
|
||||
|
||||
echo ""
|
||||
echo "=== Готово ==="
|
||||
echo "Запуск: ./build/bin/cards"
|
||||
echo ""
|
||||
echo "Пароль от БД можно задать через переменную окружения:"
|
||||
echo " DB_PASSWORD=secret ./build/bin/cards"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
struct ServerConfig {
|
||||
std::string address;
|
||||
int port;
|
||||
int threads;
|
||||
};
|
||||
|
||||
struct DatabaseConfig {
|
||||
std::string host;
|
||||
int port;
|
||||
std::string dbname;
|
||||
std::string user;
|
||||
std::string password;
|
||||
};
|
||||
|
||||
struct AppConfig {
|
||||
ServerConfig server;
|
||||
DatabaseConfig database;
|
||||
|
||||
static AppConfig loadFromFile(const std::string& filename) {
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Cannot open config file: " + filename);
|
||||
}
|
||||
|
||||
json j;
|
||||
file >> j;
|
||||
|
||||
AppConfig config;
|
||||
config.server.address = j.at("server").at("address");
|
||||
config.server.port = j.at("server").at("port");
|
||||
config.server.threads = j.at("server").at("threads");
|
||||
config.database.host = j.at("database").value("host", "localhost");
|
||||
config.database.port = j.at("database").value("port", 5432);
|
||||
config.database.dbname = j.at("database").value("dbname", "");
|
||||
config.database.user = j.at("database").value("user", "");
|
||||
config.database.password = j.at("database").value("password", "");
|
||||
|
||||
// Environment variables override config file
|
||||
if (const char* val = std::getenv("DB_HOST")) config.database.host = val;
|
||||
if (const char* val = std::getenv("DB_PORT")) config.database.port = std::stoi(val);
|
||||
if (const char* val = std::getenv("DB_NAME")) config.database.dbname = val;
|
||||
if (const char* val = std::getenv("DB_USER")) config.database.user = val;
|
||||
if (const char* val = std::getenv("DB_PASSWORD")) config.database.password = val;
|
||||
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#include "database.hpp"
|
||||
#include <chrono>
|
||||
|
||||
Database::Database(const DatabaseConfig& cfg)
|
||||
: conn(std::make_unique<pqxx::connection>(
|
||||
"host=" + cfg.host +
|
||||
" port=" + std::to_string(cfg.port) +
|
||||
" dbname=" + cfg.dbname +
|
||||
" user=" + cfg.user +
|
||||
" password=" + cfg.password)),
|
||||
rng(std::random_device{}()) {
|
||||
ensureTableExists();
|
||||
}
|
||||
|
||||
void Database::ensureTableExists() {
|
||||
pqxx::work txn(*conn);
|
||||
|
||||
// Создаем таблицу, если её нет
|
||||
txn.exec(
|
||||
"CREATE TABLE IF NOT EXISTS random_data ("
|
||||
" id SERIAL PRIMARY KEY,"
|
||||
" name VARCHAR(100) NOT NULL,"
|
||||
" value INTEGER NOT NULL,"
|
||||
" description TEXT,"
|
||||
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||
")"
|
||||
);
|
||||
|
||||
// Проверяем, есть ли данные
|
||||
auto result = txn.exec("SELECT COUNT(*) FROM random_data");
|
||||
int count = result[0][0].as<int>();
|
||||
|
||||
// Если данных нет, заполняем тестовыми данными
|
||||
if (count == 0) {
|
||||
txn.exec(
|
||||
"INSERT INTO random_data (name, value, description) VALUES "
|
||||
"('Alpha', 100, 'First test record'), "
|
||||
"('Beta', 200, 'Second test record'), "
|
||||
"('Gamma', 300, 'Third test record'), "
|
||||
"('Delta', 400, 'Fourth test record'), "
|
||||
"('Epsilon', 500, 'Fifth test record'), "
|
||||
"('Zeta', 600, 'Sixth test record'), "
|
||||
"('Eta', 700, 'Seventh test record'), "
|
||||
"('Theta', 800, 'Eighth test record'), "
|
||||
"('Iota', 900, 'Ninth test record'), "
|
||||
"('Kappa', 1000, 'Tenth test record')"
|
||||
);
|
||||
}
|
||||
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
json Database::getRandomRecord() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
pqxx::work txn(*conn);
|
||||
|
||||
// Получаем общее количество записей
|
||||
auto countResult = txn.exec("SELECT COUNT(*) FROM random_data");
|
||||
int totalCount = countResult[0][0].as<int>();
|
||||
|
||||
if (totalCount == 0) {
|
||||
return json{
|
||||
{"error", "No records found in database"}
|
||||
};
|
||||
}
|
||||
|
||||
// Генерируем случайный ID
|
||||
std::uniform_int_distribution<int> idDist(1, totalCount);
|
||||
int randomId = idDist(rng);
|
||||
|
||||
// Запрашиваем случайную запись
|
||||
auto result = txn.exec_params(
|
||||
"SELECT id, name, value, description, created_at "
|
||||
"FROM random_data "
|
||||
"WHERE id = $1",
|
||||
randomId
|
||||
);
|
||||
|
||||
if (result.empty()) {
|
||||
// Если не нашли, берем первую (на всякий случай)
|
||||
result = txn.exec("SELECT id, name, value, description, created_at FROM random_data LIMIT 1");
|
||||
}
|
||||
|
||||
const auto& row = result[0];
|
||||
|
||||
json response = {
|
||||
{"id", row[0].as<int>()},
|
||||
{"name", row[1].as<std::string>()},
|
||||
{"value", row[2].as<int>()},
|
||||
{"description", row[3].as<std::string>()},
|
||||
{"created_at", row[4].as<std::string>()},
|
||||
{"timestamp", std::chrono::system_clock::now().time_since_epoch().count()}
|
||||
};
|
||||
|
||||
txn.commit();
|
||||
return response;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <pqxx/pqxx>
|
||||
#include <string>
|
||||
#include <random>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "common.hpp"
|
||||
|
||||
class Database {
|
||||
public:
|
||||
explicit Database(const DatabaseConfig& config);
|
||||
~Database() = default;
|
||||
|
||||
json getRandomRecord();
|
||||
|
||||
private:
|
||||
std::unique_ptr<pqxx::connection> conn;
|
||||
std::mt19937 rng;
|
||||
std::mutex mtx;
|
||||
// Создание тестовой таблицы, если она не существует
|
||||
void ensureTableExists();
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
#include <iostream>
|
||||
#include <exception>
|
||||
#include <csignal>
|
||||
#include <atomic>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "database.hpp"
|
||||
#include "server.hpp"
|
||||
|
||||
std::atomic<bool> stopServer{false};
|
||||
|
||||
void signalHandler(int signal) {
|
||||
if (signal == SIGINT || signal == SIGTERM) {
|
||||
std::cout << "\nReceived shutdown signal. Stopping server..." << std::endl;
|
||||
stopServer = true;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
try {
|
||||
// Загрузка конфигурации
|
||||
std::string configFile = "config.json";
|
||||
if (argc > 1) {
|
||||
configFile = argv[1];
|
||||
}
|
||||
|
||||
std::cout << "Loading configuration from: " << configFile << std::endl;
|
||||
AppConfig config = AppConfig::loadFromFile(configFile);
|
||||
|
||||
// Инициализация базы данных
|
||||
std::cout << "Connecting to database..." << std::endl;
|
||||
auto db = std::make_shared<Database>(config.database);
|
||||
std::cout << "Database connected successfully!" << std::endl;
|
||||
|
||||
// Инициализация и запуск сервера
|
||||
Server server(
|
||||
config.server.address,
|
||||
config.server.port,
|
||||
config.server.threads,
|
||||
db
|
||||
);
|
||||
|
||||
// Установка обработчиков сигналов
|
||||
std::signal(SIGINT, signalHandler);
|
||||
std::signal(SIGTERM, signalHandler);
|
||||
|
||||
std::cout << "Server is running on "
|
||||
<< config.server.address << ":"
|
||||
<< config.server.port
|
||||
<< " with " << config.server.threads << " threads" << std::endl;
|
||||
std::cout << "Press Ctrl+C to stop" << std::endl;
|
||||
|
||||
// Запуск сервера в отдельном потоке
|
||||
std::thread serverThread([&server]() {
|
||||
server.run();
|
||||
});
|
||||
|
||||
// Ожидание сигнала остановки
|
||||
while (!stopServer) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
std::cout << "Stopping server..." << std::endl;
|
||||
server.stop();
|
||||
serverThread.join();
|
||||
|
||||
std::cout << "Server stopped successfully!" << std::endl;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
#include "server.hpp"
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
// Implementation of HttpSession
|
||||
HttpSession::HttpSession(net::ip::tcp::socket&& socket, std::shared_ptr<Database> db)
|
||||
: stream(std::move(socket)), db(db) {}
|
||||
|
||||
void HttpSession::run() {
|
||||
readRequest();
|
||||
}
|
||||
|
||||
void HttpSession::readRequest() {
|
||||
req = {};
|
||||
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) {
|
||||
http::response<http::string_body> res{http::status::ok, request.version()};
|
||||
res.set(http::field::server, "Boost.Beast Server");
|
||||
res.set(http::field::content_type, "application/json");
|
||||
|
||||
try {
|
||||
if (request.method() == http::verb::get && request.target() == "/") {
|
||||
// Получаем случайный объект из БД
|
||||
json response = db->getRandomRecord();
|
||||
res.body() = response.dump(4);
|
||||
} else if (request.method() == http::verb::get && request.target() == "/health") {
|
||||
json response = {
|
||||
{"status", "ok"},
|
||||
{"timestamp", std::chrono::system_clock::now().time_since_epoch().count()}
|
||||
};
|
||||
res.body() = response.dump(4);
|
||||
} else {
|
||||
res.result(http::status::not_found);
|
||||
json error = {
|
||||
{"error", "Not Found"},
|
||||
{"path", request.target()}
|
||||
};
|
||||
res.body() = error.dump(4);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
res.result(http::status::internal_server_error);
|
||||
json error = {
|
||||
{"error", "Internal Server Error"},
|
||||
{"message", e.what()}
|
||||
};
|
||||
res.body() = error.dump(4);
|
||||
}
|
||||
|
||||
res.prepare_payload();
|
||||
writeResponse(std::move(res));
|
||||
}
|
||||
|
||||
void HttpSession::writeResponse(http::response<http::string_body>&& res) {
|
||||
auto self = shared_from_this();
|
||||
http::async_write(
|
||||
stream,
|
||||
res,
|
||||
[self](beast::error_code ec, std::size_t) {
|
||||
if (ec) {
|
||||
self->handleError(ec, "write");
|
||||
}
|
||||
// Close the connection gracefully
|
||||
self->stream.socket().shutdown(net::ip::tcp::socket::shutdown_send, ec);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
{}
|
||||
|
||||
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::make_shared<HttpSession>(std::move(socket), db)->run();
|
||||
accept();
|
||||
} else {
|
||||
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(); }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "common.hpp"
|
||||
#include "database.hpp"
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace http = beast::http;
|
||||
namespace net = boost::asio;
|
||||
|
||||
|
||||
class HttpSession : public std::enable_shared_from_this<HttpSession> {
|
||||
public:
|
||||
HttpSession(net::ip::tcp::socket&& socket, std::shared_ptr<Database> db);
|
||||
void run();
|
||||
|
||||
private:
|
||||
void readRequest();
|
||||
void onRead(beast::error_code ec, std::size_t bytes_transferred);
|
||||
void processRequest(http::request<http::string_body>&& req);
|
||||
void writeResponse(http::response<http::string_body>&& res);
|
||||
void handleError(beast::error_code ec, const char* what);
|
||||
|
||||
beast::tcp_stream stream;
|
||||
beast::flat_buffer buffer;
|
||||
std::shared_ptr<Database> db;
|
||||
http::request<http::string_body> req;
|
||||
};
|
||||
|
||||
class Server {
|
||||
public:
|
||||
Server(const std::string& address, int port, int threads, std::shared_ptr<Database> db);
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void accept();
|
||||
void onAccept(beast::error_code ec, net::ip::tcp::socket socket);
|
||||
|
||||
net::io_context ioc;
|
||||
net::ip::tcp::acceptor acceptor;
|
||||
std::shared_ptr<Database> db;
|
||||
int threads;
|
||||
std::vector<std::thread> threadPool;
|
||||
net::steady_timer acceptTimer;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue