Введение
Как создавать RESTful-сервисы эффективно и мощно? Используя возможности производительности C++. Научимся настройке сервера, обработке HTTP-запросов и парсингу JSON при помощи таких библиотек, как Boost.Beast и nlohmann/json.
RESTful API-интерфейсы на C++
Что такое RESTful API?
Это основа современной веб-разработки для стандартизированного и масштабируемого взаимодействия клиентских приложений с серверами. REST расшифровывается как REpresentational State Transfer, то есть «передача состояния представления». Это архитектурный стиль, которым для доступа к веб-ресурсам и работы с ними используется протокол передачи данных без сохранения состояния, обычно HTTP.
RESTful API-интерфейсы эффективны и удобны благодаря соблюдению ряда принципов и ограничений:
- Несохраняемость состояния. В каждом запросе клиента к серверу должна содержаться вся информация, необходимая для понимания и обработки запроса. Поскольку между запросами сервером не сохраняется никакого клиентского контекста, это взаимодействие без состояния.
- Идентификация ресурсов. Ресурсы идентифицируются по URL-адресам, то есть единообразным указателям их местонахождения. Каждый ресурс представлен уникальным URL-адресом, поэтому доступ к ресурсу и работа с ним упрощаются.
- Единый интерфейс. В RESTful API операции над ресурсами выполняются стандартными HTTP-методами: GET, POST, PUT, DELETE, PATCH и другими. Благодаря такому единообразию упрощаются проектирование и понимание API.
- Представление ресурсов. Ресурсы представлены в различных форматах: JSON, XML или обычный текст. Самый ходовой, простой и удобный — JSON, или нотация объектов JavaScript.
- Взаимодействие без состояний. Чтобы выполниться сервером, каждый запрос клиента должен содержать всю необходимую информацию. Этим обеспечивается независимая обработка каждого запроса.
Преимущества C++ для RESTful API
C++ славится производительностью и эффективностью, это отличный выбор для создания высокопроизводительных RESTful API. И вот почему:
- Производительность. В C++ обеспечивается низкоуровневый доступ к памяти и системным ресурсам, благодаря чему оптимизируется производительность. Особенно полезно это для API, которым требуются высокая пропускная способность и низкая задержка.
- Контроль. Благодаря детализированному контролю над системными ресурсами и памятью разработчики здесь создают высокоэффективные приложения.
- Конкурентность. В C++ поддерживаются многопоточность и конкурентность, важные для эффективной обработки одновременных запросов к API.
- Серьезные библиотеки. В экосистему C++ включены такие мощные библиотеки, как Boost.Beast для обмена данными по HTTP и веб-сокетам и nlohmann/json для парсинга JSON. Этими библиотеками упрощается процесс разработки, расширяются возможности API.
- Платформонезависимая разработка. На C++ RESTful API-интерфейсы создаются и развертываются в Windows, Linux и macOS.
Ключевые компоненты RESTful API
Разберем ключевые компоненты для создания RESTful API на C++:
- HTTP-сервер. Им обрабатываются входящие HTTP-запросы и отправляются соответствующие ответы. С библиотекой Boost.Beast создание HTTP-серверов упрощается.
- Маршрутизация. Ею определяются способы обработки различных HTTP-запросов, сопоставляются URL-адреса с конкретными функциями или методами, которыми эти запросы обрабатываются.
- Обработка запросов. Обработчиками запросов обрабатываются входящие запросы, выполняются необходимые операции, генерируются соответствующие ответы. Обычно это происходит при взаимодействии с базой данных или другими серверными службами.
- Генерирование ответов. Ответы генерируются на основе результата обработки запроса. При этом задаются код состояния HTTP, заголовки и содержимое тела ответа.
- Парсинг JSON. JSON — распространенный формат данных для взаимодействия с API. Парсинг и генерирование данных в формате JSON необходимы для обработки запросов и ответов. Для этой цели пригодится библиотека nlohmann/json.
Настройка среды разработки
Прежде чем приступить к реализации, настроим среду разработки и установим:
- Современный компилятор C++: GCC, Clang или MSVC.
- Генератор систем сборки CMake, им упрощается процесс сборки.
- Библиотеки Boost, коллекцию рецензируемых платформонезависимых библиотек с исходным кодом на C++. Загружаем отсюда.
- Популярную JSON-библиотеку nlohmann/json для C++, загружаем из репозитория GitHub или включаем единым заголовочным файлом.
Установив необходимые инструменты и библиотеки, приступим к созданию RESTful API.
Пример приложения
Продемонстрируем процесс создания RESTful API на C++ на примере простого приложения с базовыми CRUD-операциями — создание, чтение, изменение, удаление — для управления коллекцией данных. В него включаются:
- Инициализация сервера: настройка HTTP-сервера с Boost.Beast.
- Маршрутизация: определение маршрутов для конечных точек API.
- Обработка запросов: реализация обработчиков для HTTP-методов: GET, POST, PUT, DELETE.
- Парсинг 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++, обеспечивая их эффективность и сопровождаемость.
Документация
- Boost.Beast.
- Справочник по C++.
- Официальная документация по CMake.
Читайте также:
- Почему разрабатывать веб-интерфейсы так сложно?
- JMeter-тестирование динамической нагрузки Restful API
- Отладка API Java Message Service с использованием Lightrun
Читайте нас в Telegram, VK и Дзен
Перевод статьи Alexander Obregon: Building RESTful APIs with C++