Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

каркас сервера posts #5

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions server_posts/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.17)
project(server_posts)
set(CMAKE_CXX_STANDARD 20)

# set(CMAKE_CXX_FLAGS "-g -Wall -lpthread -lgtest -L/usr/local/lib")
set(CMAKE_CXX_FLAGS "-g -Wall -lpthread -lgtest -L/usr/local/lib -lpqxx -lpq")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

попробуй сделать через cmake большую часть флагов, например find_package(PostgreSQL)



#find_package(PQXX)
find_package(GTest REQUIRED)
find_package(Threads REQUIRED)




find_package(Boost)
include_directories(${Boost_INCLUDE_DIRS})

set(DIR ${CMAKE_CURRENT_SOURCE_DIR})

set(INC_DIR ${DIR}/include)
set(SRC_DIR ${DIR}/src)


add_library(server_posts STATIC
${SRC_DIR}/main.cpp
${SRC_DIR}/database.cpp include/post.h)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

в библиотеке не может быть функции main()


add_executable(server_posts_test src/main.cpp)
target_link_libraries(server_posts_test ${Boost_LIBRARIES} Threads::Threads pthread)



49 changes: 49 additions & 0 deletions server_posts/include/database.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Created by steve on 05.12.2020.
//

#ifndef POSTS_DATABASE_H
#define POSTS_DATABASE_H

#include <string>
#include <pqxx/pqxx>
#include <map>
#include <iostream>
#include <boost/format.hpp>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

много лишних include в хедере



#include "../include/post.h"
// Класс Post
// id поста
// id пользователя создавшего пост
// дата создания поста
// title поста
// text поста
// attach url картинок

class PostsDataBase {
public:
// Конектится к базе данных, создается объет таблицы
PostsDataBase();

~PostsDataBase();

std::string create_post(int user_id, Post& post);
std::string update_post(int user_id, Post& updated);
std::string delete_post(int user_id, int post_id);

std::vector<Post> get_all_posts();
std::vector<Post> get_user_posts(int user_id);
std::vector<Post> get_posts_for_user(int fuser_id);
Post& get_one_post(int post_id);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

методы, которые не изменяют поведения объекты, должны иметь префикс const
std::vector<Post> get_posts_for_user(int fuser_id) const


bool is_opened();

private:
// Table Posts_;
pqxx::connection database_;
void do_modifying_request(const std::string& sql_request);
pqxx::result do_select_request(const std::string& sql_request);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

для работы с БД надо создать wrapper, в которые надо инкапсулировать функции

};

#endif //POSTS_DATABASE_H
109 changes: 109 additions & 0 deletions server_posts/include/handlers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// Created by steve on 07.12.2020.
//

#ifndef POSTS_HANDLERS_H
#define POSTS_HANDLERS_H

#include "types.h"
#include "database.h"
#include "../include/utility_parser.h"



std::string for_user(const std::string& id_request, const std::map<std::string, size_t>& args) {
// TODO: реализовать работу с БД
std::string result = "id_request: " + id_request +
" posts for user with id: " + std::to_string(args.at("user_id"));
return result;
}

std::string all_posts(const std::string& id_request, const std::map<std::string, size_t>& args) {
// TODO: реализовать работу с БД
std::string result = "id_request: " + id_request + " all posts\n";

std::vector<Post> vec_posts = db.get_all_posts();
std::string posts = vec_posts_to_str(vec_posts);
return result + posts;
}

std::string user_posts(std::string const& id_request, std::map<std::string, size_t> const& args) {
// TODO: реализовать работу с БД
std::string result = "id_request: " + id_request +
" user`s posts with id: " + std::to_string(args.at("user_id"));
return result;
}

std::string one_post(std::string const& id_request, std::map<std::string, size_t> const& args) {
// TODO: реализовать работу с БД
std::string result = "id_request: " + id_request +
" post with id: " + std::to_string(args.at("post_id"));
return result;
}
std::string delete_post(std::string const& id_request, std::map<std::string, size_t> const& args) {
// TODO: реализовать работу с БД
std::string result = "id_request: " + id_request +
" deleting a post with id: " + std::to_string(args.at("post_id")) +
" by a user with id: " + std::to_string(args.at("user_id"));
return result;
}

std::string create_post(std::string const& id_request,
std::map<std::string, size_t> const& args,
std::string const& body) {
// TODO: реализовать работу с БД
Post new_post = parse_body(body);
boost::format parsed_body =
(boost::format(
"post_id: %1%,\n"
" creator_id: %2%,\n"
" creation_date: %3%,\n"
" title: %4%,\n"
" text: %5%,\n"
" attach: %6%\n")
%new_post.post_id
%new_post.creator_id
%new_post.creation_date
%new_post.title
%new_post.text
%new_post.attach);
std::string body_str = boost::str(parsed_body);
std::string result = "id_request: "
+ id_request
+ " creating a post by a user with id: "
+ std::to_string(args.at("user_id"))
+ "\n" + body_str;
return result;
}

std::string update_post(std::string const& id_request,
std::map<std::string, size_t> const& args,
std::string const& body) {
// TODO: реализовать работу с БД
Post for_update = parse_body(body);
boost::format parsed_body =
(boost::format(
"post_id: %1%,\n"
" creator_id: %2%,\n"
" creation_date: %3%,\n"
" title: %4%,\n"
" text: %5%,\n"
" attach: %6%\n")
%for_update.post_id
%for_update.creator_id
%for_update.creation_date
%for_update.title
%for_update.text
%for_update.attach);
std::string body_str = boost::str(parsed_body);
std::string result = "id_request: "
+ id_request
+ " changing a post with id: "
+ std::to_string(args.at("post_id"))
+ " by a user with id: "
+ std::to_string(args.at("user_id"))
+ "\n" + body_str;;
return result;
}

#endif //POSTS_HANDLERS_H
33 changes: 33 additions & 0 deletions server_posts/include/post.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Created by steve on 10.12.2020.
//

#ifndef SERVER_POSTS_POST_H
#define SERVER_POSTS_POST_H


class Post {
public:
Post(
int post_id,
int creator_id,
std::string creation_date,
std::string title,
std::string text,
std::string attach) : post_id(post_id),
creator_id(creator_id),
creation_date(std::move(creation_date)),
title(std::move(title)),
text(std::move(text)),
attach(std::move(attach)) {}

int post_id;
int creator_id;
std::string creation_date{};
std::string title{};
std::string text{};
std::string attach{};

};

#endif //SERVER_POSTS_POST_H
68 changes: 68 additions & 0 deletions server_posts/include/server.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// Created by steve on 04.12.2020.
//

#ifndef POSTS_SERVER_H
#define POSTS_SERVER_H

// как работает сервер
// 1 Принимаем входящее соединение на заданном порту
// 2 Создаем объект сеанса для принятого соединения
// 3 Переходим к шагу 1

// как работают сессии
// 1 Считываем строку до тех пор, пока не будет найден символ \n
// 2 Парсинг типа команды и ее аргументов
// 3 Вызываем обработчик ответственный за эту команду, и формируем ответную строку
// 4 Отправляем ответ клиенту

#include "../include/session.h"
#include "../include/handlers.h"

class TCPServer {
public:

TCPServer(io::io_context& io_context, std::uint16_t port)
: io_context(io_context)
, acceptor (io_context, tcp::endpoint(tcp::v4(), port)) {

accept();
}


void add_endpoint() {
dispatcher.emplace("/posts/fuser/", dispatcher_entry{1, for_user});
dispatcher.emplace("/posts/all/", dispatcher_entry{0, all_posts});
dispatcher.emplace("/posts/user/", dispatcher_entry{1, user_posts});
dispatcher.emplace("/posts/post/", dispatcher_entry{1, one_post});
dispatcher.emplace("/posts/dlt/", dispatcher_entry{2, delete_post});
// ендпоинты требующие боди
dispatcher_with_body.emplace(
"/posts/create/",
dispatcher_entry_with_body{1, create_post}
);
dispatcher_with_body.emplace(
"/posts/upd/",
dispatcher_entry_with_body{2, update_post}
);
}

private:

void accept() {
socket.emplace(io_context);

acceptor.async_accept(*socket, [&] (error_code error) {
std::make_shared<Session>(std::move(*socket), dispatcher, dispatcher_with_body)->Session::start();
accept();
});
}
io::io_context& io_context;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

зачем тут ссылка?

tcp::acceptor acceptor;
std::optional<tcp::socket> socket;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

почему socket optional???

dispatcher_type dispatcher; // map обработчиков команд описан в types.hpp
dispatcher_type_with_body dispatcher_with_body;
};


#endif // POSTS_SERVER_H
146 changes: 146 additions & 0 deletions server_posts/include/session.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#include "../include/types.h"
#include "../include/utility_parser.h"



class Session : public std::enable_shared_from_this<Session> {
public:

Session(tcp::socket&& socket,
dispatcher_type const& dispatcher,
dispatcher_type_with_body const& dispatcher_with_body)
: socket (std::move(socket))
, dispatcher(dispatcher)
, dispatcher_with_body(dispatcher_with_body) {}

void start() {
write("POSTS server is ready to serve");
write();
read();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

мне не реализация этой функции

}

private:

void write(std::string const& string) {
outgoing.push(string + "\r\n> ");
}


void read() {
io::async_read_until(
socket,
incoming,
"\n",
std::bind(&Session::on_read, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}


void on_read(error_code error, std::size_t bytes_transferred) {
if(!error) {
std::istream stream(&incoming);
std::string line;
std::getline(stream, line);
incoming.consume(bytes_transferred);
boost::algorithm::trim(line);
if(!line.empty()) {
dispatch(line);
}
read();
}
}

// TODO: реализовать функцию отправки на сокет http сервера

void write() {
io::async_write(
socket,
io::buffer(outgoing.front()),
std::bind(&Session::on_write, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}


void on_write(error_code error, std::size_t bytes_transferred) {
if(!error) {
outgoing.pop();
if(!outgoing.empty()) {
write();
}
}
}


// Находим соответствующий обработчик команд, применяем его,
// если он найден, отправляем ответ обратно.
void dispatch(std::string const& line) {
std::stringstream response; // строка ответа
auto parameters_request = split(line, " ");

if (parameters_request.size() == 2) { // боди нет
try {
RequestWithoutBody result = parse_without_body(parameters_request);
if(auto it = dispatcher.find(result.command); it != dispatcher.cend()) { // поиск хендлера
auto const& entry = it->second;
if(entry.args == result.args.size()) {
try {
response << entry.handler(result.id_request, result.args); // вызов хендлера
} catch(std::exception const& e) {
response << "404";
}
} else {
response << "404";
}
} else {
response << "404";
}
} catch(boost::bad_lexical_cast &) {
response << "404";
}

} else if (parameters_request.size() == 3) { // боди есть
try {
RequestWithBody result = parse_with_body(parameters_request);
if(auto it = dispatcher_with_body.find(result.command); it != dispatcher_with_body.cend()) { // поиск хендлера
auto const& entry = it->second;
if(entry.args == result.args.size()) {
try {
response << entry.handler(result.id_request, result.args, result.body); // вызов хендлера
} catch(std::exception const& e) {
response << "404";
}
} else {
response << "404";
}
} else {
response << "404";
}
} catch(boost::bad_lexical_cast &) {
response << "404";
}
} else {
response << "404";
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

слишком большая вложенность, вложенность больше 3-х плохо читается


// Поместим ответ в исходящую очередь
write(response.str());

// Если очередь была пуста до этого, то мы должны инициировать доставку сообщения обратно клиенту
if(outgoing.size() == 1) {
write();
}
}

// Сеанс считывает входящие данные с помощью async_read_until до тех пор,
// пока не будет найден символ \n, а затем анализирует полученную строку.

// TODO: добавить еще сокет для отправки на сразу на http сервер
tcp::socket socket;
// сохраняем диспетчер внутри объекта сервера и передаем ссылку на него в сеанс
dispatcher_type const& dispatcher;
dispatcher_type_with_body const& dispatcher_with_body;
io::streambuf incoming;
std::queue<std::string> outgoing;
};




57 changes: 57 additions & 0 deletions server_posts/include/types.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#pragma once

#include <iostream>
#include <optional>
#include <queue>
#include <functional>

#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
#include <boost/date_time.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/tokenizer.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/foreach.hpp>



namespace io = boost::asio;
namespace ip = io::ip;
using tcp = ip::tcp;
using error_code = boost::system::error_code;
using namespace std::placeholders;

using boost::property_tree::ptree;

using string_group = std::vector<std::string>;

struct dispatcher_entry {
std::size_t const args; // количество аргументов
// bool body_required;
std::function<std::string (const std::string& , const std::map<std::string, size_t>& )> const handler;
};

struct dispatcher_entry_with_body {
std::size_t const args; // количество аргументов
// bool body_required;
std::function<std::string (const std::string& , const std::map<std::string, size_t>&, const std::string& )> const handler;
};

// map обработчиков команд (название - диспетчер)
using dispatcher_type = std::map<std::string, dispatcher_entry>;
using dispatcher_type_with_body = std::map<std::string, dispatcher_entry_with_body>;


using separator = boost::char_separator<char>;
using tokenizer = boost::tokenizer<separator>;
// вспомогательная функция, которая разбивает заданную строку на вектор строк
// drop - символ по которому разбивается строка
string_group split(std::string const& string, const char *drop) {
string_group group;
for(auto&& str : tokenizer(string, separator(drop))) {
group.emplace_back(str);
}
return group;
}
145 changes: 145 additions & 0 deletions server_posts/include/utility_parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//
// Created by steve on 09.12.2020.
//

#ifndef SERVER_POSTS_UTILITY_PARSER_H
#define SERVER_POSTS_UTILITY_PARSER_H

#include "../include/types.h"
#include "../include/post.h"




class RequestWithBody {
public:
RequestWithBody(
std::string id_request,
std::string command,
std::map<std::string, size_t> args,
std::string body) :
id_request(std::move(id_request)),
command(std::move(command)),
args(std::move(args)),
body(std::move(body)) {}

std::string id_request;
std::string command;
std::map<std::string, size_t> args;
std::string body;
};

class RequestWithoutBody {
public:
RequestWithoutBody(
std::string id_request,
std::string command,
std::map<std::string, size_t> args
) :
id_request(std::move(id_request)),
command(std::move(command)),
args(std::move(args)) {}

std::string id_request;
std::string command;
std::map<std::string, size_t> args;
};



Post parse_body(const std::string& body_str) {
std::stringstream ss;
ss << body_str;

std::map<std::string, std::string> result;
ptree pt;
boost::property_tree::read_json(ss, pt);
// if (pt.empty()) {std::cout << "bad body" << std::endl;}
for (ptree::const_iterator it = pt.begin(); it != pt.end(); ++it) {
result[std::string(it->first)] = it->second.get_value<std::string>();
}

Post post(
boost::lexical_cast<std::uint16_t>(result["post_id"]),
boost::lexical_cast<std::uint16_t>(result["user_id"]),
result["creation_date"],
result["title"],
result["text"],
result["attach"]
);
return post;
}

std::map<std::string, size_t> get_url_parameters(const string_group& vector_parameters) {
std::map<std::string, size_t> result;
if (vector_parameters.size() % 2 != 0) {
std::cout << "bad vector_parameters" << std::endl;
}
for (size_t i = 0; i < vector_parameters.size() - 1; i += 2) {
result[vector_parameters[i]] = boost::lexical_cast<std::uint16_t>(vector_parameters[i + 1]);
}
return result;
}


RequestWithoutBody parse_without_body(const string_group& args) {
std::string id_request = args[0];
std::string url_request = args[1];

string_group url_param = split(url_request, "?");
std::string command = url_param[0];
std::map<std::string, size_t> parameters;

if (url_param.size() == 2) { // если query string есть
std::string query_string = url_param[1];
parameters = get_url_parameters(split(query_string, "=&"));
}
return RequestWithoutBody(id_request, command, parameters);
}


RequestWithBody parse_with_body(const string_group& args) {
std::string id_request = args[0];
std::string url_request = args[1];
std::string body_request = args[2];

string_group url_param = split(url_request, "?");
std::string command = url_param[0];
std::map<std::string, size_t> parameters;

if (url_param.size() == 2) { // если query string есть
std::string query_string = url_param[1];
parameters = get_url_parameters(split(query_string, "=&"));
}
return RequestWithBody(id_request, command, parameters, body_request);
}

std::string vec_posts_to_str(std::vector<Post>& vec_posts) {
std::string res = "";
for (const auto& i : vec_posts) {
boost::format parsed_body =
(boost::format(
"post_id: %1%,\n"
" creator_id: %2%,\n"
" creation_date: %3%,\n"
" title: %4%,\n"
" text: %5%,\n"
" attach: %6%\n\n")
%i.post_id
%i.creator_id
%i.creation_date
%i.title
%i.text
%i.attach);
std::string body_str = boost::str(parsed_body);
res += body_str;
}
return res;
}
//try {
// dispatch("123 /posts/all/ {}");
//} catch(boost::bad_lexical_cast &) {
// std::cout << "hui" << std::endl;
//}

#endif //SERVER_POSTS_UTILITY_PARSER_H
88 changes: 88 additions & 0 deletions server_posts/src/database.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//
// Created by steve on 04.12.2020.
//
#include "../include/database.h"

PostsDataBase::PostsDataBase() :
database_("dbname=db_posts host=localhost user=amartery password=amartery") {}

PostsDataBase::~PostsDataBase() {
database_.disconnect();
}

void PostsDataBase::do_modifying_request(const std::string& sql_request) {
pqxx::work W(database_);
W.exec(sql_request);
W.commit();
}

pqxx::result PostsDataBase::do_select_request(const std::string& sql_request) {
pqxx::nontransaction N(database_);
return N.exec(sql_request);
}

std::string PostsDataBase::create_post(int user_id, Post& post) {
boost::format creating_sql_req =
(boost::format(
"insert into "
"posts(post_id, creator_id, creation_date, title, text, attach) "
"values (%1%, %2%, '%3%', '%4%', '%5%', '%6%')")
%post.post_id
%post.creator_id
%post.creation_date
%post.title
%post.text
%post.attach);
std::string sql_req = boost::str(creating_sql_req);
do_modifying_request(sql_req);
return "OK";
}


std::string PostsDataBase::update_post(int user_id, Post& updated) {

}

std::string PostsDataBase::delete_post(int user_id, int post_id) {

}


std::vector<Post> PostsDataBase::get_all_posts() {
std::string sql_request = "select * from posts";
pqxx::result r = do_select_request(sql_request);
std::vector<Post> result;
for (pqxx::result::const_iterator c = r.begin(); c != r.end(); ++c) {
Post temp(
c[0].as<int>(),
c[1].as<int>(),
c[2].as<std::string>(),
c[3].as<std::string>(),
c[4].as<std::string>(),
c[5].as<std::string>());
result.push_back(temp);
}
return result;
}


std::vector<Post> PostsDataBase::get_user_posts(int user_id) {

}
std::vector<Post> PostsDataBase::get_posts_for_user(int fuser_id) {

}
Post& PostsDataBase::get_one_post(int post_id) {

}

bool PostsDataBase::is_opened() {
if (database_.is_open()) {
std::cout << "Соединение с бд открыто" << std::endl;
return true;
}
std::cout << "Соединение с бд закрыто" << std::endl;
return false;
}


31 changes: 31 additions & 0 deletions server_posts/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Created by steve on 04.12.2020.
//

#include "../include/server.h"

// id_request /posts/fuser/?user_id=% - Запрос постов для конкретного пользователя
// id_request /posts/all/ - Запрос всех постов
// id_request /posts/user/?user_id=% - Запрос постов конкретного пользователя
// id_request /posts/post/?post_id=% - Запрос определённого поста из БД
// id_request /posts/dlt/?post_id=%&user_id=% - Запрос на удаление post_id поста пользователем id (второй параметр)
// id_request /posts/create/?user_id=% {body} - Запрос на создание поста пользователем id
// id_request /posts/upd/?user_id=%&post_id=% {body} - Запрос на изменение поста post_id пользователем id (второй параметр)



int main() {

io::io_context io_context;
TCPServer server(io_context, 2348); // boost::lexical_cast<std::uint16_t>(argv[1])
server.add_endpoint();
io_context.run();

return 0;
}

// "123 /posts/upd/?user_id=3&post_id=7"
// "123 /posts/all/"
// "123 /posts/upd/?user_id=3&post_id=7 {\"post_id\":0,\"user_id\":0,\"creation_date\":\"2016-08-29T09:12:33.001Z\",\"title\":\"string\",\"text\":\"string\",\"attach\":\"string\"}"