Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Исходник Aero. C++23 networking library with WebSocket and HTTP implementations built on asio

Stop Staring At the Shadows
Участник
Участник
Статус
Оффлайн
Регистрация
10 Окт 2020
Сообщения
536
Реакции
529
1778248398181.png

Репозиторий:
Пожалуйста, авторизуйтесь для просмотра ссылки.


Всем привет. Aero это TLS-optional, async-first библиотека для современных клиентских приложений которые не хотят тащить в проект тяжелый boost-beast который требует ~50 различных boost модулей, или устаревший websocketpp который не обновлялся уже более 5 лет и имеет не самый приятный API. Так же в пользу относительной легкости, библиотека из коробки поддерживает wolfssl, билды которого в десятки раз меньше по размеру чем билды OpenSSL. Имплементация не является универсально максимально легкой по размеру генерируемого кода, речь скорее об "относительной" легкости, - aero не является сильно тяжелее оригинального standalone-asio на котором aero написан, и в сравнении с тем же boost-beast, количество зависимостей, размер артефакта после билда и скорость компиляции гораздо лучше.

Библиотека собирается и проходит 300+ юнит тестов на MSVC (Debug/Release & x64/x86, так как
Пожалуйста, авторизуйтесь для просмотра ссылки.
), Clang, Clang-cl, GCC и MacOS AppleClang компиляторах. Имплементация WebSocket так же протестирована
Пожалуйста, авторизуйтесь для просмотра ссылки.
, который используют крупнейшие имплементации WebSocket протокола (Google Chrome, Mozilla Firefox, Internet Explorer и т.д.).
Из зависимостей у Aero сейчас: Asio, utfcpp, опционально TLS библиотека (openssl или wolfssl).

Aero старается объединять в себе лучшее из всех миров: простоту, детальность и производительность. Библиотека не отбирает у вас возможность детального контроля над транспортным слоем, кастомными заголовками, опциями и прочими надстройками, при этом не переходя к сильным компромиссам с производительностью, а для простоты - оборачивает привычные операции и дефолты таким образом, что самые частые юзкейсы становится использовать крайне просто и очевидно любому программисту понимающему C++.
Async слой построен на asio completion tokens, что позволяет вам использовать любой вид перегрузок который только возможен: callbacks, use_awaitable, use_future, redirect_error, as_tuple, cancel_after и так далее, список большой. Те, кто знаком с asio уже поняли что такой дизайн упрощает жизнь и вариативность кода в различных сценариях в разы.
Aero так же не использует mutexы для синхронизации (только в одном очень редком случае внутри переиспользуемого пула подключений для HTTP 1.1), используя механизм asio::strand во избежание лока всего потока ради синхронизации доступа к чему-либо.
Ниже 2 максимально простых примера использующие синхронные методы.
Больше примеров (в том числе и асинхронных) вы найдёте тут:
Пожалуйста, авторизуйтесь для просмотра ссылки.

HTTP synchronous example:
Expand Collapse Copy
#include <print>

#include "aero/http.hpp"

namespace http = aero::http;

int main() {
  std::expected<http::response, std::error_code> response = http::get("https://example.com/");
  if (!response) {
    std::println("Request failed: {}", response.error().message());
    return 1;
  }

  std::println("Received response from example.com:");

  std::println("Response Headers:");
  for (const auto& [name, value] : response->headers) {
    std::println("{}: {}", name, value);
  }
 
  std::println("Status: {} ({})", response->status_line.reason_phrase, response->status_code());

  if (response->content_type() == "text/html") {
    std::println("Body (first 100 bytes): {}", response->text().substr(0, 100));
  } else {
    std::println("Body: {}", response->text());
  }
}

WebSocket TLS Binance Stream:
Expand Collapse Copy
#include <print>

#include "aero/deadline.hpp"
#include "aero/error.hpp"
#include "aero/tls/system_context.hpp"
#include "aero/tls/version.hpp"
#include "aero/websocket/close_code.hpp"
#include "aero/websocket/tls/client.hpp"

namespace websocket = aero::websocket;
namespace tls = aero::tls;
namespace http = aero::http;

void print_error(std::string_view message, const std::error_code& ec) {
  std::println("{}: {} ({} - {})", message, ec.message(), ec.value(), ec.category().name());
}

void print_headers(const http::headers& headers) {
  std::println("[HEADERS] Printing:");
  for (const auto& [name, value] : headers) {
    std::println("{}: {}", name, value);
  }
  std::println("[HEADERS] Done");
}

int main() {
  using namespace std::chrono_literals;

  // System context simply wraps asio::ssl::context and implements
  // AIA fetching for Win32, otherwise sets default verify paths
  tls::system_context tls_ctx{tls::version::tlsv1_2};
  tls_ctx.disable_deprecated_versions();

  websocket::tls::client client{tls_ctx.context()};

  auto handshake_headers = client.connect("wss://stream.binance.com:9443/ws/btcusdt@trade", 5s);
  if (!handshake_headers) {
    print_error("Connect to binance stream failed", handshake_headers.error());
    return 1;
  }

  std::println("Succesfully connected");
  print_headers(*handshake_headers);

  aero::deadline deadline{5min};

  for (;;) {
    if (deadline.expired()) {
      break;
    }

    auto message = client.read(deadline.remaining());
    if (!message) {
      if (message.error() == aero::error::errc::timeout && deadline.expired()) {
        std::println("Read deadline expired, breaking from read-loop");
        break;
      }
      print_error("Failed to receive message from binance stream", message.error());
      break;
    }

    if (!message->is_text()) {
      std::println("Received non-text message type ({}), skipping", message->kind);
      continue;
    }

    std::println("Received message from binance stream: {}", message->text());
  }

  auto close_ec = client.close(websocket::close_code::normal, "thank you, we are leaving.");
  if (close_ec) {
    print_error("Close handshake failed", close_ec);
    std::ignore = client.force_close();
  }

  return 0;
}

Библиотека написана в header-only стиле для упрощения интеграции в проекты, возможно в будущем так же добавлю single-header вариант.
Так же имплементирован TLS слой как отдельный модуль внутри библиотеки, внутри которого имплементирован aero::tls::system_context который использует кастомный verify-callback с AIA фетчингом для Windows билдов (через WinCrypt API), который при возможности и необходимости подтянет недостающие промежуточные сертификаты (в случае если peer отдал неполную цепочку сертификатов). Для справки: никто не заставляет вас использовать TLS слой из aero, основной способ передачи внешнего tls-context это передавать asio::ssl::context.

Синхронные обёртки сейчас имплементированы объективно говоря не лучшим образом, они используют блокирующий future с async вариантом соответствующей функции, что не даст вам использовать синхронные обёртки в контексте того же потока на котором запущен io_context (при попытке вам вернёт ошибку aero::basic_error::deadlock_would_occur), поэтому это "не настоящая синхронность".

В отличии от boost-beast, aero имеет не настолько сильные требования к контролю записи как это делает beast. Beast требует синхронизации всех asio::async_write в websocket соединение, по вполне очевидной причине того что asio::async_write это composed операция, объединяющая в себе N вызовов к asio::async_write_some, из-за чего вполне возможен интерливинг между записями из разных потоков, то есть часть сообщения из потока A может "вклиниться" в сообщение, которое пишет поток B.
Aero решает эту проблему
Пожалуйста, авторизуйтесь для просмотра ссылки.
которая естественным образом упорядочивает все записи в него

Подробнее о библиотеке и API можно прочитать в README репозитория.

Спасибо всем тем кто использует библиотеку и ставит звёзды. Если у вас появятся какие-либо проблемы при её использовании, создавайте Issue на Github. Буду рад каждому контрибьютору.
 
Последнее редактирование:
Назад
Сверху Снизу