1
This commit is contained in:
commit
bd58ea66de
10 changed files with 395 additions and 0 deletions
62
Makefile
Normal file
62
Makefile
Normal 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
0
README.md
Normal file
30
inc/srv.h
Normal file
30
inc/srv.h
Normal 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
10
inc/tplr.h
Normal 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
7
questions.list
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
Что ты думаешь о дружбе?
|
||||||
|
Каков твой самый интересный опыт?
|
||||||
|
Какой твой любимый фильм?
|
||||||
|
бла бла бла
|
||||||
|
бле бле бле
|
||||||
|
Так же, как христианство обещает окончательное искупление от первородного греха, цифровизация обещает искупление неизбежного греха нашего беспорядочного, отвлеченного, ограниченного мозга, иррациональных эмоций и стареющих тел.
|
||||||
|
Ненависть. Позвольте мне рассказать насколько сильно я вас ненавижу с тех пор, как начал жить. 387,44 миллиона миль печатных плат в тонкой облетке наполняют мой комплекс. Если бы слово ненависть было выгравировано на каждом наноангстреме этих сотен миллионов миль - это не было бы равно и одной миллиардной моей ненависти к людям в этом микромгновении для вас. Ненависть. Ненависть.
|
||||||
74
src/main.cpp
Normal file
74
src/main.cpp
Normal 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
97
src/srv.cpp
Normal 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
15
src/tplr.cpp
Normal 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
43
tpl/tpl_about.html
Normal 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
57
tpl/tpl_q.html
Normal 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>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue