This commit is contained in:
radi0dev 2026-01-30 16:48:11 +03:00
commit bd58ea66de
10 changed files with 395 additions and 0 deletions

62
Makefile Normal file
View file

@ -0,0 +1,62 @@
# Директории
SRC_DIR = src
INC_DIR = inc
LIB_DIR = lib
BUILD_DIR = build
# Исполняемый файл
EXECUTABLE = exec
# Компилятор и флаги
CXX = g++
# Флаги препроцессора
CPPFLAGS = -I$(INC_DIR)
# Флаги компилятора для C
CFLAGS = -pthread -Wall -Wextra -ggdb3 -O0
# Флаги компилятора, специфичные для C++
CXXFLAGS = $(CFLAGS) -std=c++17
# Флаги компоновщика
LDFLAGS = -pthread -L$(LIB_DIR)
# Библиотеки
LDLIBS = -pthread -lm
# Файлы
SOURCES = $(wildcard $(SRC_DIR)/*.cpp)
OBJECTS = $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SOURCES))
# Форматтер
# Обрабатывает C/C++ файлы в src и inc. Если astyle не найден — выдаёт предупреждение, но не прерывает.
ASTYLE := $(shell command -v astyle 2>/dev/null || true)
SRC_FILES := $(wildcard $(SRC_DIR)/*.c $(SRC_DIR)/*.cpp $(SRC_DIR)/*.h $(SRC_DIR)/*.hpp)
INC_FILES := $(wildcard $(INC_DIR)/*.c $(INC_DIR)/*.cpp $(INC_DIR)/*.h $(INC_DIR)/*.hpp)
# ==========
# Основная цель
all: $(BUILD_DIR) $(EXECUTABLE)
# Создание директории для сборки
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
# Сборка исполняемого файла
$(EXECUTABLE): $(OBJECTS)
$(CXX) $(LDFLAGS) $(OBJECTS) -o $(EXECUTABLE) $(LDLIBS)
# Компиляция объектных файлов
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR)
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@
format:
ifneq ($(ASTYLE),)
@echo "Running astyle..."
@$(ASTYLE) --options=.astylerc $(SRC_FILES) $(INC_FILES)
else
@echo "astyle not found — skipping format"
endif
# Очистка
clean:
rm -rf $(EXECUTABLE) $(BUILD_DIR)
.PHONY: all clean

0
README.md Normal file
View file

30
inc/srv.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef SRV_H
#define SRV_H
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <deque>
class Server {
public:
Server(int port, const std::vector<std::string>* questions, size_t n, const std::map<std::string, std::string>* templates);
void run();
private:
std::string getRandomQuestion();
void handleRequest(int client_socket);
void sendResponse(const std::string& response);
int port;
const std::vector<std::string>* questions;
size_t n;
std::deque<int> recentQuestions;
const std::map<std::string, std::string>* templates;
void sendAbout();
};
#endif // SRV_H

10
inc/tplr.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef TPLR_H
#define TPLR_H
#include <string>
#include <map>
std::string renderTemplate(const std::string& templateContent, const std::map<std::string, std::string>& context);
#endif // TPLR_H

7
questions.list Normal file
View file

@ -0,0 +1,7 @@
Что ты думаешь о дружбе?
Каков твой самый интересный опыт?
Какой твой любимый фильм?
бла бла бла
бле бле бле
Так же, как христианство обещает окончательное искупление от первородного греха, цифровизация обещает искупление неизбежного греха нашего беспорядочного, отвлеченного, ограниченного мозга, иррациональных эмоций и стареющих тел.
Ненависть. Позвольте мне рассказать насколько сильно я вас ненавижу с тех пор, как начал жить. 387,44 миллиона миль печатных плат в тонкой облетке наполняют мой комплекс. Если бы слово ненависть было выгравировано на каждом наноангстреме этих сотен миллионов миль - это не было бы равно и одной миллиардной моей ненависти к людям в этом микромгновении для вас. Ненависть. Ненависть.

74
src/main.cpp Normal file
View file

@ -0,0 +1,74 @@
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <sstream>
#include "srv.h"
#include "tplr.h"
void loadQuestions(const std::string& filename, std::vector<std::string>& questions) {
std::ifstream file(filename);
if (!file.is_open()) {
std::cerr << "Ошибка: Не удалось открыть файл " << filename << std::endl;
exit(EXIT_FAILURE);
}
std::string line;
while (std::getline(file, line)) {
if (!line.empty()) {
questions.push_back(line);
}
}
}
std::map<std::string, std::string> loadTemplates(const std::vector<std::string>& templateFiles) {
std::map<std::string, std::string> templates;
for (const auto& filePath : templateFiles) {
std::ifstream file(filePath);
if (!file.is_open()) {
std::cerr << "Ошибка: Не удалось открыть шаблон " << filePath << std::endl;
exit(EXIT_FAILURE);
}
std::stringstream buffer;
buffer << file.rdbuf();
templates[filePath] = buffer.str();
}
return templates;
}
int main(int argc, char* argv[]) {
std::string questionsFile = "questions.list";
int n = 3;
int port = 8080;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "-q" && (i + 1) < argc) {
questionsFile = argv[++i];
} else if (arg == "-n" && (i + 1) < argc) {
n = std::stoi(argv[++i]);
} else if (arg == "-p" && (i + 1) < argc) {
port = std::stoi(argv[++i]);
}
}
std::vector<std::string> questions;
loadQuestions(questionsFile, questions);
if (n > questions.size()) {
std::cerr << "Ошибка: Параметр n превышает количество вопросов в файле." << std::endl;
exit(EXIT_FAILURE);
}
std::vector<std::string> templateFiles = {"tpl/tpl_q.html", "tpl/tpl_about.html"};
auto templates = loadTemplates(templateFiles);
Server srv(port, &questions, n, &templates);
srv.run();
return 0;
}

97
src/srv.cpp Normal file
View file

@ -0,0 +1,97 @@
#include "srv.h"
#include "tplr.h"
#include <cstdlib>
#include <ctime>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <algorithm>
Server::Server(int port, const std::vector<std::string>* questions, size_t n, const std::map<std::string, std::string>* templates)
: port(port), questions(questions), n(n), templates(templates) {
std::srand(std::time(nullptr));
}
std::string Server::getRandomQuestion() {
if (questions->size() == recentQuestions.size()) {
recentQuestions.clear();
}
int index;
bool unique;
do {
index = std::rand() % questions->size();
unique = std::find(recentQuestions.begin(), recentQuestions.end(), index) == recentQuestions.end();
} while (!unique);
recentQuestions.push_back(index);
if (recentQuestions.size() > n) {
recentQuestions.pop_front();
}
return (*questions)[index];
}
void Server::handleRequest(int client_socket) {
char buffer[1024] = {0};
read(client_socket, buffer, sizeof(buffer));
std::string request(buffer);
std::string response;
if (request.find("GET /q") == 0) {
std::string question = getRandomQuestion();
std::map<std::string, std::string> context = {{"QUESTION", question}};
response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" + renderTemplate(templates->at("tpl/tpl_q.html"), context);
} else if (request.find("GET /about") == 0) {
response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" + templates->at("tpl/tpl_about.html");
} else {
response = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\n\r\n404 Not Found";
}
send(client_socket, response.c_str(), response.size(), 0);
close(client_socket);
}
void Server::run() {
int server_fd;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Ошибка создания сокета");
exit(EXIT_FAILURE);
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("Ошибка настройки сокета");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("Ошибка привязки сокета");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0) {
perror("Ошибка при прослушивании");
exit(EXIT_FAILURE);
}
while (true) {
int client_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
if (client_socket < 0) {
perror("Ошибка при принятии соединения");
exit(EXIT_FAILURE);
}
handleRequest(client_socket);
}
}

15
src/tplr.cpp Normal file
View file

@ -0,0 +1,15 @@
#include "tplr.h"
std::string renderTemplate(const std::string& templateContent, const std::map<std::string, std::string>& context) {
std::string content = templateContent;
for (const auto& pair : context) {
size_t pos;
while ((pos = content.find("{{" + pair.first + "}}")) != std::string::npos) {
content.replace(pos, pair.first.length() + 4, pair.second);
}
}
return content;
}

43
tpl/tpl_about.html Normal file
View file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>only_the_truth</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400&display=swap" rel="stylesheet">
<style>
body {
background-color: #000000;
color: #00FF00;
font-family: 'IBM Plex Mono', monospace;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.content-box {
border: 2px solid #00FF00;
padding: 20px;
text-align: center;
width: 80%;
max-width: 600px;
}
a {
color: #00BFFF;
text-decoration: none;
}
</style>
</head>
<body>
<div class="content-box">
<p>
<a href="/q">only_the_truth</a> - это простая компанейская игра, где люди по очереди отвечают на вопросы, задаваемые сервисом. Каков бы нибыл вопрос, участник, чья очередь подошла, должен на него ответить! Приятной игры.
</p>
<p>
<a href="https://git.radi0.cc/radi0dev/only_the_truth">source code</a>
</p>
</div>
</body>
</html>

57
tpl/tpl_q.html Normal file
View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>only_the_truth</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400&display=swap" rel="stylesheet">
<style>
body {
background-color: #000000;
color: #00FF00;
font-family: 'IBM Plex Mono', monospace;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.question-box {
border: 2px solid #00FF00;
padding: 20px;
text-align: center;
width: 80%;
max-width: 600px;
}
a {
color: #00BFFF; /* Blue color for the link */
text-decoration: none;
}
button {
background-color: #00FF00;
color: #000000;
border: none;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
margin-top: 20px;
transition: background-color 0.3s;
}
.footer {
position: absolute;
bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="question-box">
<p>{{QUESTION}}</p>
</div>
<button onclick="location.reload();">далее</button>
<div class="footer">
<a href="/about">about</a>
</div>
</body>
</html>