Створення RESTful API інтерфейсів на C++

pic

Вступ

Як ефективно та потужно створювати RESTful-сервіси? Використовуючи можливості продуктивності C++. Ми навчимося налаштовувати сервер, обробляти HTTP-запити та парсити JSON за допомогою таких бібліотек, як Boost.Beast та nlohmann/json.

RESTful API на C++

Що таке RESTful API?

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

RESTful API є ефективними та зручними завдяки дотриманню ряду принципів та обмежень:

1.
Без збереження стану. У кожному запиті клієнта до сервера повинна бути вся інформація, необхідна для розуміння та обробки цього запиту. Оскільки між запитами сервер не зберігає жодного контексту клієнта, таке взаємодія є безстатевим.

  1. Ідентифікація ресурсів. Ресурси ідентифікуються за допомогою URL-адрес, тобто єдинообразних вказівників їхнього місцезнаходження. Кожен ресурс має унікальну URL-адресу, що спрощує доступ до ресурсу та роботу з ним.

  2. Єдиний інтерфейс. У RESTful API операції над ресурсами виконуються стандартними HTTP-методами: GET, POST, PUT, DELETE, PATCH та іншими. Завдяки такій однорідності спрощується проектування та розуміння API.

  3. Представлення ресурсів. Ресурси представлені в різних форматах: 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.
  4. Платформонезалежна розробка. На 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-сервера

Щоб створити простий HTTP-сервер за допомогою Boost.Beast, який буде прослуховувати вхідні з'єднання на вказаному порті та отримувати базове відповідне повідомлення, створюємо файл main.cpp і додаємо такий код:

#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   

namespace beast = boost::beast; // з «»  
namespace http = beast::http; // з «»  
namespace net = boost::asio; // з «»  
using tcp = net::ip::tcp; // з «»  

// Ця функція генерує HTTP-відповідь на запит.

http::response handle_request(http::request const& req) {  
 // Відповідаємо на запит «GET» повідомленням «"Hello, World!"»  
 if (req.method() == http::verb::get) {  
 http::response 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::status::bad_request, req.version()};  
}  

// Цей клас обробляє підключення HTTP-сервера.

class Session : public std::enable_shared_from_this<Session> {  
 tcp::socket socket_;  
 beast::flat_buffer buffer_;  
 http::request 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 res) {  
 auto self(shared_from_this());  
 auto sp = std::make_shared<http::response>(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 << "Помилка відкриття: " << ec.message() << std::endl;  
 return;  
 }  

 // Дозволяємо повторне використання адреси  
 acceptor_.set_option(net::socket_base::reuse_address(true), ec);  
 if (ec) {  
 std::cerr << "Помилка налаштування опції: " << ec.message() << std::endl;  
 return;  
 }  

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

 // Починаємо прослуховування підключень  
 acceptor_.listen(net::socket_base::max_listen_connections, ec);  
 if (ec) {  
 std::cerr << "Помилка прослуховування: " << 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 << "Помилка: " << 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-запит.
  • Функція перевіряє HTTP-метод запиту.

Якщо це GET-запит, створюється HTTP-відповідь з кодом стану 200 OK, в типі вмісту задається text/plain, а в тілі — “Hello, World!”.
- Потім відповідь повертається, готова до відправлення клієнту.

http::response handle_request(http::request const& req) {  
 if (req.method() == http::verb::get) {  
 http::response 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::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 {  
 tcp::socket socket_;  
 beast::flat_buffer buffer_;  
 http::request 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 res) {  
 auto self(shared_from_this());  
 auto sp = std::make_shared>(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 {  
 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(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(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 handle_request(http::request 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 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 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 = "Оновлено: " + json_request.dump();  
 nlohmann::json json_response = {{"message", response_message}};  
 http::response 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", "Ресурс видалено"}};  
 http::response 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::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   
#include 
cpp
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   

namespace beast = boost::beast; // з «»  
namespace http = beast::http; // з «»  
namespace net = boost::asio; // з «»  
using tcp = net::ip::tcp; // з «»  

// Ця функція повертає HTTP-відповідь на запит.
cpp
http::response handle_request(http::request 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 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 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 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 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::status::bad_request, req.version()};  
}  

// Цим класом обробляється підключення HTTP-сервера.
cpp
class Session : public std::enable_shared_from_this<Session> {  
 tcp::socket socket_;  
 beast::flat_buffer buffer_;  
 http::request 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 res) {  
 auto self(shared_from_this());  
 auto sp = std::make_shared<http::response>(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);  
 });  
 }  
};  

// Цим класом приймаються вхідні підключення, запускаються сеанси.
cpp
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-адресів повертається відповідь на некоректний запит.

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

Заключення

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

Ми розглянули основи налаштування сервера, обробки HTTP-запитів та взаємодії з даними JSON.
Застосовуючи ці інструменти та прийоми роботи, ви зможете створити та розширити власні RESTful API на C++, забезпечуючи їх ефективність та супроводжуваність.

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

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

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

Переклад статті Alexander Obregon: Будування RESTful API за допомогою C++

Перекладено з: Создание RESTful API-интерфейсов на C++

Leave a Reply

Your email address will not be published. Required fields are marked *