Введение

Как создавать RESTful-сервисы эффективно и мощно? Используя возможности производительности C++. Научимся настройке сервера, обработке HTTP-запросов и парсингу JSON при помощи таких библиотек, как Boost.Beast и nlohmann/json.

RESTful API-интерфейсы на C++

Что такое RESTful API?

Это основа современной веб-разработки для стандартизированного и масштабируемого взаимодействия клиентских приложений с серверами. REST расшифровывается как REpresentational State Transfer, то есть «передача состояния представления». Это архитектурный стиль, которым для доступа к веб-ресурсам и работы с ними используется протокол передачи данных без сохранения состояния, обычно HTTP.

RESTful API-интерфейсы эффективны и удобны благодаря соблюдению ряда принципов и ограничений:

  1. Несохраняемость состояния. В каждом запросе клиента к серверу должна содержаться вся информация, необходимая для понимания и обработки запроса. Поскольку между запросами сервером не сохраняется никакого клиентского контекста, это взаимодействие без состояния.
  2. Идентификация ресурсов. Ресурсы идентифицируются по URL-адресам, то есть единообразным указателям их местонахождения. Каждый ресурс представлен уникальным URL-адресом, поэтому доступ к ресурсу и работа с ним упрощаются.
  3. Единый интерфейс. В RESTful API операции над ресурсами выполняются стандартными HTTP-методами: GET, POST, PUT, DELETE, PATCH и другими. Благодаря такому единообразию упрощаются проектирование и понимание API.
  4. Представление ресурсов. Ресурсы представлены в различных форматах: JSON, XML или обычный текст. Самый ходовой, простой и удобный  —  JSON, или нотация объектов JavaScript.
  5. Взаимодействие без состояний. Чтобы выполниться сервером, каждый запрос клиента должен содержать всю необходимую информацию. Этим обеспечивается независимая обработка каждого запроса.

Преимущества C++ для RESTful API

C++ славится производительностью и эффективностью, это отличный выбор для создания высокопроизводительных RESTful API. И вот почему:

  1. Производительность. В C++ обеспечивается низкоуровневый доступ к памяти и системным ресурсам, благодаря чему оптимизируется производительность. Особенно полезно это для API, которым требуются высокая пропускная способность и низкая задержка.
  2. Контроль. Благодаря детализированному контролю над системными ресурсами и памятью разработчики здесь создают высокоэффективные приложения.
  3. Конкурентность. В C++ поддерживаются многопоточность и конкурентность, важные для эффективной обработки одновременных запросов к API.
  4. Серьезные библиотеки. В экосистему C++ включены такие мощные библиотеки, как Boost.Beast для обмена данными по HTTP и веб-сокетам и nlohmann/json для парсинга JSON. Этими библиотеками упрощается процесс разработки, расширяются возможности API.
  5. Платформонезависимая разработка. На C++ RESTful API-интерфейсы создаются и развертываются в Windows, Linux и macOS.

Ключевые компоненты RESTful API

Разберем ключевые компоненты для создания RESTful API на C++:

  1. HTTP-сервер. Им обрабатываются входящие HTTP-запросы и отправляются соответствующие ответы. С библиотекой Boost.Beast создание HTTP-серверов упрощается.
  2. Маршрутизация. Ею определяются способы обработки различных HTTP-запросов, сопоставляются URL-адреса с конкретными функциями или методами, которыми эти запросы обрабатываются.
  3. Обработка запросов. Обработчиками запросов обрабатываются входящие запросы, выполняются необходимые операции, генерируются соответствующие ответы. Обычно это происходит при взаимодействии с базой данных или другими серверными службами.
  4. Генерирование ответов. Ответы генерируются на основе результата обработки запроса. При этом задаются код состояния HTTP, заголовки и содержимое тела ответа.
  5. Парсинг JSON. JSON  —  распространенный формат данных для взаимодействия с API. Парсинг и генерирование данных в формате JSON необходимы для обработки запросов и ответов. Для этой цели пригодится библиотека nlohmann/json.

Настройка среды разработки

Прежде чем приступить к реализации, настроим среду разработки и установим:

  1. Современный компилятор C++: GCC, Clang или MSVC.
  2. Генератор систем сборки CMake, им упрощается процесс сборки.
  3. Библиотеки Boost, коллекцию рецензируемых платформонезависимых библиотек с исходным кодом на C++. Загружаем отсюда.
  4. Популярную JSON-библиотеку nlohmann/json для C++, загружаем из репозитория GitHub или включаем единым заголовочным файлом.

Установив необходимые инструменты и библиотеки, приступим к созданию RESTful API.

Пример приложения

Продемонстрируем процесс создания RESTful API на C++ на примере простого приложения с базовыми CRUD-операциями  —  создание, чтение, изменение, удаление  —  для управления коллекцией данных. В него включаются:

  1. Инициализация сервера: настройка HTTP-сервера с Boost.Beast.
  2. Маршрутизация: определение маршрутов для конечных точек API.
  3. Обработка запросов: реализация обработчиков для HTTP-методов: GET, POST, PUT, DELETE.
  4. Парсинг JSON: использование nlohmann/json для анализа и генерирования данных в формате JSON.

К концу статьи у вас должно сложиться фундаментальное представление о том, как создавать RESTful API на C++, расширять и настраивать приложение под свои задачи.

Настройка сервера с Boost.Beast

Boost.Beast

Boost.Beast  —  библиотека C++ для обмена данными по HTTP и веб-сокетам, в основу которой положена Boost.Asio. Это мощный, гибкий способ построения сетевых приложений, отличный выбор для создания RESTful API. С Boost.Beast многое в работе с протоколами HTTP упрощается, а разработчики сосредотачиваются на реализации логики приложений.

Настройка проекта

Прежде чем погрузиться в код, настроим новый проект на C++.

Создаем для него каталог, а для управления процессом сборки и включения необходимых зависимостей настраиваем такой файл CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)
project(RestfulApi)

set(CMAKE_CXX_STANDARD 17)

find_package(Boost REQUIRED COMPONENTS system filesystem)
include_directories(${Boost_INCLUDE_DIRS})

add_executable(RestfulApi main.cpp)
target_link_libraries(RestfulApi ${Boost_LIBRARIES})

В этой конфигурации указываются требуемая для проекта версия CMake 3.10 или новее и используемый стандарт C++17, а также отыскиваются и привязываются к проекту необходимые компоненты Boost system и filesystem.

Создание HTTP-сервера

Чтобы сделать с помощью Boost.Beast простой HTTP-сервер для прослушивания входящих соединений на указанном порту и получения базового ответного сообщения, создаем файл main.cpp и добавляем такой код:

#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 <boost/config.hpp>
#include <iostream>
#include <memory>
#include <string>
#include <thread>

namespace beast = boost::beast; // из «<boost/beast.hpp>»
namespace http = beast::http; // из «<boost/beast/http.hpp>»
namespace net = boost::asio; // из «<boost/asio.hpp>»
using tcp = net::ip::tcp; // из «<boost/asio/ip/tcp.hpp>»

// Этой функцией выдается HTTP-ответ на запрос.
http::response<http::string_body> handle_request(http::request<http::string_body> const& req) {
// Отвечаем на запрос «GET» сообщением «"Hello, World!"»
if (req.method() == http::verb::get) {
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, "Beast");
res.set(http::field::content_type, "text/plain");
res.keep_alive(req.keep_alive());
res.body() = "Hello, World!";
res.prepare_payload();
return res;
}

// Ответ по умолчанию для неподдерживаемых методов
return http::response<http::string_body>{http::status::bad_request, req.version()};
}

// Этим классом обрабатывается подключение HTTP-сервера.
class Session : public std::enable_shared_from_this<Session> {
tcp::socket socket_;
beast::flat_buffer buffer_;
http::request<http::string_body> req_;

public:
explicit Session(tcp::socket socket) : socket_(std::move(socket)) {}

void run() {
do_read();
}

private:
void do_read() {
auto self(shared_from_this());
http::async_read(socket_, buffer_, req_, [this, self](beast::error_code ec, std::size_t) {
if (!ec) {
do_write(handle_request(req_));
}
});
}

void do_write(http::response<http::string_body> res) {
auto self(shared_from_this());
auto sp = std::make_shared<http::response<http::string_body>>(std::move(res));
http::async_write(socket_, *sp, [this, self, sp](beast::error_code ec, std::size_t) {
socket_.shutdown(tcp::socket::shutdown_send, ec);
});
}
};

// Этим классом принимаются входящие подключения, запускаются сеансы.
class Listener : public std::enable_shared_from_this<Listener> {
net::io_context& ioc_;
tcp::acceptor acceptor_;

public:
Listener(net::io_context& ioc, tcp::endpoint endpoint)
: ioc_(ioc), acceptor_(net::make_strand(ioc)) {
beast::error_code ec;

// Открываем приемник
acceptor_.open(endpoint.protocol(), ec);
if (ec) {
std::cerr << "Open error: " << ec.message() << std::endl;
return;
}

// Разрешаем повторное использование адреса
acceptor_.set_option(net::socket_base::reuse_address(true), ec);
if (ec) {
std::cerr << "Set option error: " << ec.message() << std::endl;
return;
}

// Привязываемся к адресу сервера
acceptor_.bind(endpoint, ec);
if (ec) {
std::cerr << "Bind error: " << ec.message() << std::endl;
return;
}

// Начинаем прослушивание подключений
acceptor_.listen(net::socket_base::max_listen_connections, ec);
if (ec) {
std::cerr << "Listen error: " << ec.message() << std::endl;
return;
}

do_accept();
}

private:
void do_accept() {
acceptor_.async_accept(net::make_strand(ioc_), [this](beast::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<Session>(std::move(socket))->run();
}
do_accept();
});
}
};

int main() {
try {
auto const address = net::ip::make_address("0.0.0.0");
unsigned short port = 8080;

net::io_context ioc{1};

std::make_shared<Listener>(ioc, tcp::endpoint{address, port})->run();

ioc.run();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}

Разберем роль каждого компонента кода в создании базового HTTP-сервера с Boost.Beast.

Пространства имен и псевдонимы типов:

  • Чтобы упростить код и повысить удобство его восприятия, определяем пространства имен и псевдонимы типов.
  • В namespace beast = boost::beast; сопоставляем boost::beast с beast, упрощая обращение к связанным с Beast функциям и классам.
  • В namespace http = beast::http; сопоставляем beast::http с http, благодаря чему пользуемся HTTP-функциональностью библиотеки Boost.Beast.
  • В namespace net = boost::asio; сопоставляем boost::asio с net, получая таким образом доступ к сетевым компонентам Boost.Asio.
  • С помощью using tcp = net::ip::tcp; создается псевдоним типа tcp для boost::asio::ip::tcp, который применяем для сетевых операций TCP.

Обработчик HTTP-запросов:

  • Функцией handle_request обрабатываются входящие HTTP-запросы и генерируются ответы.
  • В качестве параметра принимается объект http::request<http::string_body>, то есть HTTP-запрос.
  • Функцией проверяется HTTP-метод запроса. Если это GET-запрос, создается HTTP-ответ с кодом состояния 200 OK, в типе содержимого задается text/plain, а в теле  —  “Hello, World!”.
  • Затем ответ возвращается, готовый отправиться клиенту.
http::response<http::string_body> handle_request(http::request<http::string_body> const& req) {
if (req.method() == http::verb::get) {
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, "Beast");
res.set(http::field::content_type, "text/plain");
res.keep_alive(req.keep_alive());
res.body() = "Hello, World!";
res.prepare_payload();
return res;
}
return http::response<http::string_body>{http::status::bad_request, req.version()};
}

Класс Session:

  • Классом Session управляются отдельные клиентские подключения.
  • В нем содержатся TCP-сокет tcp::socket и буфер beast::flat_buffer для чтения данных.
  • Процесс считывания инициируется в методе run вызовом do_read.
  • В do_read с помощью http::async_read из клиента асинхронно считывается HTTP-запрос. Вызовом handle_request считанный запрос обрабатывается, а затем с помощью do_write отправляется.
  • Из do_write HTTP-ответ посредством http::async_write отправляется обратно клиенту, после чего сокет закрывается.
class Session : public std::enable_shared_from_this<Session> {
tcp::socket socket_;
beast::flat_buffer buffer_;
http::request<http::string_body> req_;

public:
explicit Session(tcp::socket socket) : socket_(std::move(socket)) {}

void run() {
do_read();
}

private:
void do_read() {
auto self(shared_from_this());
http::async_read(socket_, buffer_, req_, [this, self](beast::error_code ec, std::size_t) {
if (!ec) {
do_write(handle_request(req_));
}
});
}

void do_write(http::response<http::string_body> res) {
auto self(shared_from_this());
auto sp = std::make_shared<http::response<http::string_body>>(std::move(res));
http::async_write(socket_, *sp, [this, self, sp](beast::error_code ec, std::size_t) {
socket_.shutdown(tcp::socket::shutdown_send, ec);
});
}
}

Класс Listener:

  • Классом Listener на указанной конечной точке принимаются входящие подключения.
  • В нем содержатся контекст ввода-вывода net::io_context и TCP-приемник tcp::acceptor.
  • Конструктором приемник инициализируется и открывается, им задается параметр переиспользования адреса, приемник привязывается к конечной точке, и начинается прослушивание подключения.
  • Методом do_accept с помощью acceptor_.async_accept асинхронно принимаются входящие подключения. Принятое новое подключение обрабатывается вновь созданным объектом Session, после чего для принятия других подключений снова вызывается do_accept.
class Listener : public std::enable_shared_from_this<Listener> {
net::io_context& ioc_;
tcp::acceptor acceptor_;

public:
Listener(net::io_context& ioc, tcp::endpoint endpoint)
: ioc_(ioc), acceptor_(net::make_strand(ioc)) {
beast::error_code ec;

acceptor_.open(endpoint.protocol(), ec);
if (ec) {
std::cerr << "Open error: " << ec.message() << std::endl;
return;
}

acceptor_.set_option(net::socket_base::reuse_address(true), ec);
if (ec) {
std::cerr << "Set option error: " << ec.message() << std::endl;
return;
}

acceptor_.bind(endpoint, ec);
if (ec) {
std::cerr << "Bind error: " << ec.message() << std::endl;
return;
}

acceptor_.listen(net::socket_base::max_listen_connections, ec);
if (ec) {
std::cerr << "Listen error: " << ec.message() << std::endl;
return;
}

do_accept();
}

private:
void do_accept() {
acceptor_.async_accept(net::make_strand(ioc_), [this](beast::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<Session>(std::move(socket))->run();
}
do_accept();
});
}
};

Функция Main:

  • Функцией main инициализируется сервер, и запускается контекст ввода-вывода.
  • Ею создаются объект контекста ввода-вывода net::io_context ioc{1} и объект Listener, который привязывается к адресу 0.0.0.0 и порту 8080.
  • Объектом Listener начинается прием подключений, а с помощью ioc.run() запускается контекст ввода-вывода, его выполнение и обработка им подключений продолжится, пока работа сервера не прекратится.
int main() {
try {
auto const address = net::ip::make_address("0.0.0.0");
unsigned short port = 8080;

net::io_context ioc{1};

std::make_shared<Listener>(ioc, tcp::endpoint{address, port})->run();

ioc.run();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}

Запуск сервера

Чтобы запустить сервер, скомпилируем проект с помощью CMake и запустим полученный исполняемый файл. В терминале переходим в каталог проекта, затем запускаем такие команды:

mkdir build
cd build
cmake ..
make
./RestfulApi

Тестируем сервер в браузере или с помощью curl:

curl http://localhost:8080

С сервера должен быть получен ответ Hello, World!

Расширение сервера

Запустив базовый HTTP-сервер, дополним его обработкой HTTP-методов и парсингом полезных нагрузок JSON. Далее рассмотрим, как с обработкой HTTP-запросов и данными в формате JSON справляется библиотека nlohmann/json.

Обработка HTTP-запросов и ответов

Для функционального RESTful API важна эффективная обработка различных HTTP-методов и полезной нагрузки JSON. Расширим возможности базового сервера поддержкой HTTP-методов GET, POST, PUT, DELETE и воспользуемся библиотекой nlohmann/json для парсинга JSON.

Добавление поддержки JSON

Сначала загружаем библиотеку nlohmann/json из репозитория GitHub или включаем ее в проект напрямую единым заголовочным файлом.

Обработка запросов JSON

Начнем с обновления функции handle_request для обработки различных HTTP-методов и парсинга полезной нагрузки JSON.

  • Включение nlohmann/json: добавляем в начало файла main.cpp такую директиву include:
http::response<http::string_body> handle_request(http::request<http::string_body> const& req) {
if (req.method() == http::verb::get && req.target() == "/api/data") {
// Обрабатываем запрос «GET»
nlohmann::json json_response = {{"message", "This is a GET request"}};
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, "Beast");
res.set(http::field::content_type, "application/json");
res.keep_alive(req.keep_alive());
res.body() = json_response.dump();
res.prepare_payload();
return res;
} else if (req.method() == http::verb::post && req.target() == "/api/data") {
// Обрабатываем запрос «POST»
auto json_request = nlohmann::json::parse(req.body());
std::string response_message = "Received: " + json_request.dump();
nlohmann::json json_response = {{"message", response_message}};
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, "Beast");
res.set(http::field::content_type, "application/json");
res.keep_alive(req.keep_alive());
res.body() = json_response.dump();
res.prepare_payload();
return res;
} else if (req.method() == http::verb::put && req.target() == "/api/data") {
// Обрабатываем запрос «PUT»
auto json_request = nlohmann::json::parse(req.body());
std::string response_message = "Updated: " + json_request.dump();
nlohmann::json json_response = {{"message", response_message}};
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, "Beast");
res.set(http::field::content_type, "application/json");
res.keep_alive(req.keep_alive());
res.body() = json_response.dump();
res.prepare_payload();
return res;
} else if (req.method() == http::verb::delete_ && req.target() == "/api/data") {
// Обрабатываем запрос «DELETE»
nlohmann::json json_response = {{"message", "Resource deleted"}};
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, "Beast");
res.set(http::field::content_type, "application/json");
res.keep_alive(req.keep_alive());
res.body() = json_response.dump();
res.prepare_payload();
return res;
}

// Ответ по умолчанию для неподдерживаемых методов
return http::response<http::string_body>{http::status::bad_request, req.version()};
}

В этой расширенной функции handle_request:

  • GET-запрос: в ответ получается JSON-сообщение с указанием на GET-запрос.
  • POST-запрос: парсится тело запроса в формате JSON, создается ответное сообщение и возвращается как JSON.
  • PUT-запрос: аналогично POST-запросу, парсится тело в формате JSON, и возвращается сообщение с указанием на то, что ресурс обновлен.
  • DELETE-запрос: возвращается сообщение JSON с указанием на то, что ресурс удален.

Обработка HTTP-методов

Для обработки HTTP-методов расширяем функцию handle_request, чтобы ею проверялся тип каждого метода  —  GET, POST, PUT, DELETE  —  и соответствующим образом направлялись запросы.

Обновленный пример кода целиком

Вот весь обновленный файл main.cpp с новой функцией handle_request и необходимыми include:

#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 <boost/config.hpp>
#include <nlohmann/json.hpp>
#include <iostream>
#include <memory>
#include <string>
#include <thread>

namespace beast = boost::beast; // из «<boost/beast.hpp>»
namespace http = beast::http; // из «<boost/beast/http.hpp>»
namespace net = boost::asio; // из «<boost/asio.hpp>»
using tcp = net::ip::tcp; // из «<boost/asio/ip/tcp.hpp>»

// Этой функцией выдается HTTP-ответ на запрос.
http::response<http::string_body> handle_request(http::request<http::string_body> const& req) {
if (req.method() == http::verb::get && req.target() == "/api/data") {
// Обрабатываем запрос «GET»
nlohmann::json json_response = {{"message", "This is a GET request"}};
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, "Beast");
res.set(http::field::content_type, "application/json");
res.keep_alive(req.keep_alive());
res.body() = json_response.dump();
res.prepare_payload();
return res;
} else if (req.method() == http::verb::post && req.target() == "/api/data") {
// Обрабатываем запрос «POST»
auto json_request = nlohmann::json::parse(req.body());
std::string response_message = "Received: " + json_request.dump();
nlohmann::json json_response = {{"message", response_message}};
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, "Beast");
res.set(http::field::content_type, "application/json");
res.keep_alive(req.keep_alive());
res.body() = json_response.dump();
res.prepare_payload();
return res;
} else if (req.method() == http::verb::put && req.target() == "/api/data") {
// Обрабатываем запрос «PUT»
auto json_request = nlohmann::json::parse(req.body());
std::string response_message = "Updated: " + json_request.dump();
nlohmann::json json_response = {{"message", response_message}};
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, "Beast");
res.set(http::field::content_type, "application/json");
res.keep_alive(req.keep_alive());
res.body() = json_response.dump();
res.prepare_payload();
return res;
} else if (req.method() == http::verb::delete_ && req.target() == "/api/data") {
// Обрабатываем запрос «DELETE»
nlohmann::json json_response = {{"message", "Resource deleted"}};
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, "Beast");
res.set(http::field::content_type, "application/json");
res.keep_alive(req.keep_alive());
res.body() = json_response.dump();
res.prepare_payload();
return res;
}

// Ответ по умолчанию для неподдерживаемых методов
return http::response<http::string_body>{http::status::bad_request, req.version()};
}

// Этим классом обрабатывается подключение HTTP-сервера.
class Session : public std::enable_shared_from_this<Session> {
tcp::socket socket_;
beast::flat_buffer buffer_;
http::request<http::string_body> req_;

public:
explicit Session(tcp::socket socket) : socket_(std::move(socket)) {}

void run() {
do_read();
}

private:
void do_read() {
auto self(shared_from_this());
http::async_read(socket_, buffer_, req_, [this, self](beast::error_code ec, std::size_t) {
if (!ec) {
do_write(handle_request(req_));
}
});
}

void do_write(http::response<http::string_body> res) {
auto self(shared_from_this());
auto sp = std::make_shared<http::response<http::string_body>>(std::move(res));
http::async_write(socket_, *sp, [this, self, sp](beast::error_code ec, std::size_t) {
socket_.shutdown(tcp::socket::shutdown_send, ec);
});
}
};

// Этим классом принимаются входящие подключения, запускаются сеансы.
class Listener : public std::enable_shared_from_this<Listener> {
net::io_context& ioc_;
tcp::acceptor acceptor_;

public:
Listener(net::io_context& ioc, tcp::endpoint endpoint)
: ioc_(ioc), acceptor_(net::make_strand(ioc)) {
beast::error_code ec;

// Открываем приемник
acceptor_.open(endpoint.protocol(), ec);
if (ec) {
std::cerr << "Open error: " << ec.message() << std::endl;
return;
}

// Разрешаем повторное использование адреса
acceptor_.set_option(net::socket_base::reuse_address(true), ec);
if (ec) {
std::cerr << "Set option error: " << ec.message() << std::endl;
return;
}

// Привязываемся к адресу сервера
acceptor_.bind(endpoint, ec);
if (ec) {
std::cerr << "Bind error: " << ec.message() << std::endl;
return;
}

// Начинаем прослушивание подключений
acceptor_.listen(net::socket_base::max_listen_connections, ec);
if (ec) {
std::cerr << "Listen error: " << ec.message() << std::endl;
return;
}

do_accept();
}

private:
void do_accept() {
acceptor_.async_accept(net::make_strand(ioc_), [this](beast::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<Session>(std::move(socket))->run();
}
do_accept();
});
}
};

int main() {
try {
auto const address = net::ip::make_address("0.0.0.0");
unsigned short port = 8080;

net::io_context ioc{1};

std::make_shared<Listener>(ioc, tcp::endpoint{address, port})->run();

ioc.run();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}

Объяснение расширенной функции handle_request:

GET-запрос:

  • Проверяется, является ли HTTP-метод GET, а целевой URL-адрес  —  /api/data.
  • Создается ответ в формате JSON с сообщением, в котором указывается, что это GET-запрос.
  • В типе содержимого ответа задается application/json, и готовится полезная нагрузка.

POST-запрос:

  • Проверяется, является ли HTTP-метод POST, а целевой URL-адрес  —  /api/data.
  • Парсится тело запроса в формате JSON с помощью nlohmann::json::parse.
  • Создается ответное сообщение с указанием полученных данных JSON.
  • Готовится ответ в формате JSON, в типе содержимого задается application/json.

PUT-запрос:

  • Проверяется, является ли HTTP-метод PUT, а целевой URL-адрес  —  /api/data.
  • Парсится тело запроса в формате JSON
  • Создается ответное сообщение с указанием обновленных данных JSON.
  • Готовится ответ в формате JSON, в типе содержимого задается application/json.

DELETE-запрос:

  • Проверяется, является ли HTTP-метод DELETE, а целевой URL-адрес  —  /api/data.
  • Создается ответ в формате JSON с сообщением, в котором указывается, что ресурс удален.
  • В типе содержимого ответа задается application/json, и готовится полезная нагрузка.

Неподдерживаемые методы:

  • Для любых неподдерживаемых HTTP-методов или URL-адресов возвращается ответ на неверный запрос.

Благодаря этой расширенной функциональности сервер с помощью различных HTTP-методов теперь справляется с базовыми CRUD-операциями и данными в формате JSON. Так формируется основа RESTful API на C++.

Заключение

Создание RESTful API с помощью C++  —  это мощный способ построения высокопроизводительных веб-сервисов. С такими библиотеками, как Boost.Beast для обмена данными по HTTP и nlohmann/json для парсинга JSON, разрабатываются надежные и масштабируемые API.

Мы изучили основы настройки сервера, обработки HTTP-запросов и взаимодействия с данными JSON. Применяя эти инструменты и приемы работы, вы сможете создать и расширить собственные RESTful API на C++, обеспечивая их эффективность и сопровождаемость.

Документация

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Alexander Obregon: Building RESTful APIs with C++

Предыдущая статьяУлучшение воспроизведения видео с помощью ExoPlayer
Следующая статьяПрогрессивное совершенствование и JavaScript-фреймворки: сложные взаимоотношения