mirror of
https://github.com/basiliscos/syncspirit.git
synced 2026-06-01 17:47:32 +00:00
811 lines
29 KiB
C++
811 lines
29 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// SPDX-FileCopyrightText: 2025 Ivan Baidakou
|
|
|
|
#include "test-utils.h"
|
|
#include "access.h"
|
|
|
|
#include "net/http_actor.h"
|
|
#include "net/resolver_actor.h"
|
|
#include "net/names.h"
|
|
#include "utils/beast_support.h"
|
|
#include "utils/format.hpp"
|
|
#include <boost/asio/ssl.hpp>
|
|
#include <boost/nowide/convert.hpp>
|
|
#include <optional>
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
using namespace syncspirit;
|
|
using namespace syncspirit::test;
|
|
using namespace syncspirit::model;
|
|
using namespace syncspirit::net;
|
|
using boost::nowide::narrow;
|
|
|
|
using configure_callback_t = std::function<void(r::plugin::plugin_base_t &)>;
|
|
using finish_callback_t = std::function<void()>;
|
|
using response_callback_t = std::function<void(message::http_response_t &message)>;
|
|
using url_callback_t = std::function<utils::uri_ptr_t(std::string_view)>;
|
|
using keep_alive_callback_t = std::function<bool()>;
|
|
|
|
auto timeout = r::pt::time_duration{r::pt::millisec{200}};
|
|
auto host = "127.0.0.1";
|
|
|
|
struct fixture_t;
|
|
|
|
struct supervisor_t : ra::supervisor_asio_t {
|
|
using ra::supervisor_asio_t::supervisor_asio_t;
|
|
|
|
void configure(r::plugin::plugin_base_t &plugin) noexcept override {
|
|
r::actor_base_t::configure(plugin);
|
|
plugin.with_casted<r::plugin::address_maker_plugin_t>([&](auto &p) {
|
|
p.set_identity("supervisor", false);
|
|
log = utils::get_logger(identity);
|
|
});
|
|
}
|
|
|
|
void shutdown_finish() noexcept override {
|
|
LOG_DEBUG(log, "shutdown_finish()");
|
|
ra::supervisor_asio_t::shutdown_finish();
|
|
if (finish_callback) {
|
|
finish_callback();
|
|
}
|
|
}
|
|
|
|
utils::logger_t log;
|
|
configure_callback_t configure_callback;
|
|
finish_callback_t finish_callback;
|
|
fixture_t *fixture;
|
|
};
|
|
using supervisor_ptr_t = r::intrusive_ptr_t<supervisor_t>;
|
|
|
|
struct client_actor_t : r::actor_base_t {
|
|
using r::actor_base_t::actor_base_t;
|
|
|
|
void configure(r::plugin::plugin_base_t &plugin) noexcept override {
|
|
r::actor_base_t::configure(plugin);
|
|
plugin.with_casted<r::plugin::address_maker_plugin_t>([&](auto &p) {
|
|
p.set_identity("client", false);
|
|
log = utils::get_logger(identity);
|
|
});
|
|
plugin.with_casted<r::plugin::registry_plugin_t>(
|
|
[&](auto &p) { p.discover_name("http", http_client, true).link(true); });
|
|
plugin.with_casted<r::plugin::starter_plugin_t>(
|
|
[&](auto &p) { p.subscribe_actor(&client_actor_t::on_response); });
|
|
}
|
|
|
|
void make_request(std::string_view path) {
|
|
using rx_buff_t = payload::http_request_t::rx_buff_ptr_t;
|
|
static constexpr std::uint32_t rx_buff_size = 1024;
|
|
|
|
auto url = url_callback(path);
|
|
auto tx_buff = utils::bytes_t();
|
|
auto keep_alive = keep_alive_callback();
|
|
|
|
http::request<http::empty_body> req;
|
|
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
|
|
req.method(http::verb::get);
|
|
req.version(11);
|
|
req.keep_alive(keep_alive);
|
|
req.target(url->encoded_target());
|
|
req.set(http::field::host, url->host());
|
|
|
|
auto ok = serialize(req, tx_buff);
|
|
assert(ok);
|
|
|
|
auto rx_buff = std::make_shared<rx_buff_t::element_type>(1024);
|
|
|
|
http_request = request<payload::http_request_t>(http_client, url, std::move(tx_buff), std::move(rx_buff),
|
|
rx_buff_size, false, true, r::message_ptr_t{})
|
|
.send(timeout);
|
|
}
|
|
|
|
void cancel_request() {
|
|
assert(http_request.has_value());
|
|
send<message::http_cancel_t::payload_t>(http_client, *http_request, get_address());
|
|
http_request.reset();
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept {
|
|
LOG_DEBUG(log, "on_response");
|
|
if (response_callback) {
|
|
response_callback(message);
|
|
}
|
|
}
|
|
|
|
r::address_ptr_t http_client;
|
|
utils::logger_t log;
|
|
std::optional<r::request_id_t> http_request;
|
|
url_callback_t url_callback;
|
|
response_callback_t response_callback;
|
|
keep_alive_callback_t keep_alive_callback;
|
|
};
|
|
using client_actor_ptr_t = r::intrusive_ptr_t<client_actor_t>;
|
|
|
|
struct fixture_t {
|
|
using acceptor_t = asio::ip::tcp::acceptor;
|
|
using message_opt_t = std::optional<http::message_generator>;
|
|
|
|
fixture_t() noexcept : ctx(io_ctx), acceptor(io_ctx), peer_sock(io_ctx) {
|
|
test::init_logging();
|
|
log = utils::get_logger("fixture");
|
|
}
|
|
|
|
void run() noexcept {
|
|
auto strand = std::make_shared<asio::io_context::strand>(io_ctx);
|
|
sup = ctx.create_supervisor<supervisor_t>().strand(strand).timeout(timeout).create_registry().finish();
|
|
sup->configure_callback = [&](r::plugin::plugin_base_t &plugin) {
|
|
plugin.template with_casted<r::plugin::starter_plugin_t>([&](auto &p) {});
|
|
};
|
|
sup->finish_callback = [&]() { finish(); };
|
|
sup->fixture = this;
|
|
sup->start();
|
|
sup->do_process();
|
|
CHECK(static_cast<r::actor_base_t *>(sup.get())->access<to::state>() == r::state_t::OPERATIONAL);
|
|
|
|
resolver_actor = make_resolver();
|
|
sup->do_process();
|
|
CHECK(static_cast<r::actor_base_t *>(resolver_actor.get())->access<to::state>() == r::state_t::OPERATIONAL);
|
|
|
|
http_actor = sup->create_actor<http_actor_t>()
|
|
.resolve_timeout(timeout / 2)
|
|
.request_timeout(timeout / 2)
|
|
.timeout(timeout)
|
|
.registry_name("http")
|
|
.ssl_verify_store(ssl_verify_store)
|
|
.keep_alive(request_keep_alive())
|
|
.finish();
|
|
sup->do_process();
|
|
|
|
CHECK(static_cast<r::actor_base_t *>(http_actor.get())->access<to::state>() == r::state_t::OPERATIONAL);
|
|
|
|
auto ep = asio::ip::tcp::endpoint(asio::ip::make_address(host), 0);
|
|
acceptor.open(ep.protocol());
|
|
acceptor.bind(ep);
|
|
acceptor.listen();
|
|
listening_ep = acceptor.local_endpoint();
|
|
log->info("acceptor is listening on {}", listening_ep);
|
|
acceptor.async_accept(peer_sock, [this](auto ec) { on_accept(ec); });
|
|
|
|
client_actor = sup->create_actor<client_actor_t>().timeout(timeout).finish();
|
|
client_actor->response_callback = [this](auto &res) { return on_response(res); };
|
|
client_actor->url_callback = [this](auto path) { return make_url(path); };
|
|
client_actor->keep_alive_callback = [this]() { return request_keep_alive(); };
|
|
io_ctx.run_for(1ms);
|
|
|
|
sup->do_process();
|
|
|
|
main();
|
|
|
|
// std::this_thread::sleep_for(1ms);
|
|
sup->do_shutdown();
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
// sup->do_process();
|
|
|
|
CHECK(static_cast<r::actor_base_t *>(http_actor.get())->access<to::state>() == r::state_t::SHUT_DOWN);
|
|
CHECK(static_cast<r::actor_base_t *>(resolver_actor.get())->access<to::state>() == r::state_t::SHUT_DOWN);
|
|
CHECK(static_cast<r::actor_base_t *>(sup.get())->access<to::state>() == r::state_t::SHUT_DOWN);
|
|
}
|
|
|
|
virtual utils::uri_ptr_t make_url(std::string_view path) {
|
|
auto url_str = fmt::format("http://{}{}", listening_ep, path);
|
|
auto url = utils::parse(url_str);
|
|
LOG_DEBUG(log, "making request url {}", url);
|
|
return url;
|
|
}
|
|
|
|
virtual void finish() {
|
|
LOG_DEBUG(log, "finish");
|
|
auto ec = sys::error_code();
|
|
acceptor.cancel(ec);
|
|
if (ec) {
|
|
LOG_DEBUG(log, "error cancelling acceptor: {}", ec.message());
|
|
}
|
|
peer_sock.cancel(ec);
|
|
if (ec) {
|
|
LOG_DEBUG(log, "error cancelling peer: {}", ec.message());
|
|
}
|
|
}
|
|
|
|
virtual bool request_keep_alive() noexcept { return false; }
|
|
|
|
virtual void main() noexcept {}
|
|
|
|
virtual void on_accept(const sys::error_code &ec) noexcept {
|
|
if (ec) {
|
|
LOG_DEBUG(log, "on_accept, ec: {}", ec.message());
|
|
return;
|
|
}
|
|
LOG_INFO(log, "on_accept, peer = {}", peer_sock.remote_endpoint());
|
|
async_read();
|
|
}
|
|
|
|
virtual void async_read() {
|
|
LOG_TRACE(log, "async_read...");
|
|
auto handler = boost::beast::bind_front_handler(&fixture_t::on_read, this);
|
|
http::async_read(peer_sock, rx_buff, request, std::move(handler));
|
|
}
|
|
|
|
virtual void async_write(http::message_generator msg, bool keep_alive) noexcept {
|
|
auto handler = boost::beast::bind_front_handler(&fixture_t::on_write, this, keep_alive);
|
|
boost::beast::async_write(peer_sock, std::move(msg), std::move(handler));
|
|
}
|
|
|
|
virtual void on_read(const sys::error_code &ec, std::size_t bytes) noexcept {
|
|
if (ec) {
|
|
LOG_DEBUG(log, "on_read, ec: {}", ec.message());
|
|
} else {
|
|
auto path = request.target();
|
|
LOG_DEBUG(log, "on_read, {} bytes, target = {}", bytes, path);
|
|
auto res_opt = handle_request(path);
|
|
if (res_opt) {
|
|
auto &msg = *res_opt;
|
|
bool keep_alive = msg.keep_alive();
|
|
LOG_DEBUG(log, "on_read, have some responce, going to send it (keep alive = {})", keep_alive);
|
|
async_write(std::move(msg), keep_alive);
|
|
}
|
|
}
|
|
rx_buff.clear();
|
|
request.clear();
|
|
}
|
|
|
|
virtual void on_write(bool keep_alive, const sys::error_code &ec, std::size_t bytes) noexcept {
|
|
if (ec) {
|
|
LOG_DEBUG(log, "on_read, ec: {}", ec.message());
|
|
return;
|
|
}
|
|
LOG_DEBUG(log, "on_write, {} bytes, keep alive = {}", bytes, keep_alive);
|
|
}
|
|
|
|
virtual message_opt_t handle_request(std::string_view path) noexcept {
|
|
if (path.find("/success") != std::string::npos) {
|
|
http::response<http::empty_body> res{http::status::ok, request.version()};
|
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
res.set(http::field::content_type, "text/html");
|
|
res.keep_alive(request.keep_alive());
|
|
res.prepare_payload();
|
|
return res;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
virtual void on_response(message::http_response_t &message) noexcept { LOG_DEBUG(log, "on_response"); }
|
|
|
|
virtual r::actor_ptr_t make_resolver() noexcept {
|
|
return sup->create_actor<resolver_actor_t>().resolve_timeout(timeout / 2).timeout(timeout).finish();
|
|
}
|
|
|
|
std::string ssl_verify_store;
|
|
asio::io_context io_ctx{1};
|
|
ra::system_context_asio_t ctx;
|
|
acceptor_t acceptor;
|
|
supervisor_ptr_t sup;
|
|
asio::ip::tcp::endpoint listening_ep;
|
|
utils::logger_t log;
|
|
asio::ip::tcp::socket peer_sock;
|
|
r::actor_ptr_t resolver_actor;
|
|
r::actor_ptr_t http_actor;
|
|
client_actor_ptr_t client_actor;
|
|
http::request<http::string_body> request;
|
|
boost::beast::flat_buffer rx_buff;
|
|
};
|
|
|
|
void test_http_start_and_shutdown() {
|
|
struct F : fixture_t {
|
|
void main() noexcept override {}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_200_ok() {
|
|
struct F : fixture_t {
|
|
void main() noexcept override {
|
|
client_actor->make_request("/success");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
REQUIRE(!ee);
|
|
|
|
auto &res = message.payload.res;
|
|
CHECK(res->response.result_int() == 200);
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_200_keep_alive() {
|
|
struct F : fixture_t {
|
|
bool request_keep_alive() noexcept override { return true; }
|
|
|
|
void main() noexcept override {
|
|
client_actor->make_request("/success-1");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
CHECK(responces == 2);
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response ({})", ++responces);
|
|
auto &ee = message.payload.ee;
|
|
REQUIRE(!ee);
|
|
auto &res = message.payload.res;
|
|
CHECK(res->response.result_int() == 200);
|
|
if (responces < 2) {
|
|
async_read();
|
|
client_actor->make_request("/success-2");
|
|
}
|
|
}
|
|
|
|
int responces = 0;
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_403_fail() {
|
|
struct F : fixture_t {
|
|
void main() noexcept override {
|
|
client_actor->make_request("/non-authorized");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
message_opt_t handle_request(std::string_view path) noexcept override {
|
|
if (path == "/non-authorized") {
|
|
http::response<http::empty_body> res{http::status::forbidden, request.version()};
|
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
res.set(http::field::content_type, "text/html");
|
|
res.keep_alive(request.keep_alive());
|
|
res.prepare_payload();
|
|
return res;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &res = message.payload.res;
|
|
CHECK(res->response.result_int() == 403);
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_garbage() {
|
|
struct F : fixture_t {
|
|
void on_read(const sys::error_code &ec, std::size_t bytes) noexcept override {
|
|
if (ec) {
|
|
LOG_DEBUG(log, "on_read, ec: {}", ec.message());
|
|
return;
|
|
}
|
|
auto path = request.target();
|
|
LOG_DEBUG(log, "on_read, {} bytes, target = {}", bytes, path);
|
|
auto buff = asio::buffer("garbage-garbage-garbage!");
|
|
boost::asio::async_write(peer_sock, buff, [this](auto ec, auto bytes) { on_write(false, ec, bytes); });
|
|
}
|
|
|
|
void main() noexcept override {
|
|
client_actor->make_request("/garbage");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
CHECK(ee);
|
|
CHECK(ee->ec);
|
|
CHECK(ee->ec.message() != "");
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_network_error() {
|
|
struct F : fixture_t {
|
|
void main() noexcept override {
|
|
client_actor->make_request("/bla-bla");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
utils::uri_ptr_t make_url(std::string_view path) override {
|
|
auto wrong_ep = decltype(listening_ep)(listening_ep.address(), 0);
|
|
auto url_str = fmt::format("http://{}{}", wrong_ep, path);
|
|
auto url = utils::parse(url_str);
|
|
LOG_DEBUG(log, "making request url {}", url);
|
|
return url;
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
CHECK(ee);
|
|
CHECK(ee->ec);
|
|
CHECK(ee->ec.message() != "");
|
|
acceptor.cancel();
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_response_timeout() {
|
|
struct F : fixture_t {
|
|
void main() noexcept override {
|
|
client_actor->make_request("/timeout");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
message_opt_t handle_request(std::string_view path) noexcept override { return {}; }
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
CHECK(ee);
|
|
CHECK(ee->ec);
|
|
CHECK(ee->ec == r::make_error_code(r::error_code_t::request_timeout));
|
|
acceptor.cancel();
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_resolve_timeout() {
|
|
struct F : fixture_t {
|
|
r::actor_ptr_t make_resolver() noexcept override {
|
|
struct sample_resolver_t : r::actor_base_t {
|
|
using r::actor_base_t::actor_base_t;
|
|
|
|
void configure(r::plugin::plugin_base_t &plugin) noexcept override {
|
|
r::actor_base_t::configure(plugin);
|
|
plugin.with_casted<r::plugin::address_maker_plugin_t>(
|
|
[&](auto &p) { p.set_identity(names::resolver, false); });
|
|
plugin.with_casted<r::plugin::registry_plugin_t>(
|
|
[&](auto &p) { p.register_name(names::resolver, get_address()); });
|
|
}
|
|
};
|
|
return sup->create_actor<sample_resolver_t>().timeout(timeout).finish();
|
|
}
|
|
|
|
void main() noexcept override {
|
|
client_actor->make_request("/resolve-timeout");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
CHECK(ee);
|
|
CHECK(ee->ec);
|
|
CHECK(ee->ec);
|
|
CHECK(ee->ec == r::make_error_code(r::error_code_t::request_timeout));
|
|
acceptor.cancel();
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_resolve_fail() {
|
|
static constexpr auto err = (int)std::errc::address_family_not_supported;
|
|
struct F : fixture_t {
|
|
r::actor_ptr_t make_resolver() noexcept override {
|
|
struct sample_resolver_t : r::actor_base_t {
|
|
using r::actor_base_t::actor_base_t;
|
|
|
|
void configure(r::plugin::plugin_base_t &plugin) noexcept override {
|
|
r::actor_base_t::configure(plugin);
|
|
plugin.with_casted<r::plugin::address_maker_plugin_t>(
|
|
[&](auto &p) { p.set_identity(names::resolver, false); });
|
|
plugin.with_casted<r::plugin::registry_plugin_t>(
|
|
[&](auto &p) { p.register_name(names::resolver, get_address()); });
|
|
plugin.with_casted<r::plugin::starter_plugin_t>(
|
|
[&](auto &p) { p.subscribe_actor(&sample_resolver_t::on_request); });
|
|
}
|
|
|
|
void on_request(message::resolve_request_t &req) noexcept {
|
|
auto ec = std::error_code(err, std::system_category());
|
|
reply_with_error(req, make_error(ec));
|
|
}
|
|
};
|
|
return sup->create_actor<sample_resolver_t>().timeout(timeout).finish();
|
|
}
|
|
|
|
void main() noexcept override {
|
|
client_actor->make_request("/resolve-timeout");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
CHECK(ee);
|
|
CHECK(ee->ec);
|
|
CHECK(ee->ec == std::error_code(err, std::system_category()));
|
|
acceptor.cancel();
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_cancellation_1() {
|
|
struct F : fixture_t {
|
|
void main() noexcept override {
|
|
client_actor->make_request("/cancellable");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
message_opt_t handle_request(std::string_view path) noexcept override {
|
|
client_actor->cancel_request();
|
|
return {};
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
CHECK(ee);
|
|
CHECK(ee->ec);
|
|
CHECK(ee->ec.message() != "");
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_cancellation_2() {
|
|
struct F : fixture_t {
|
|
void on_accept(const sys::error_code &ec) noexcept override {
|
|
client_actor->cancel_request();
|
|
LOG_INFO(log, "on_accept, cancelling request...");
|
|
if (ec) {
|
|
LOG_DEBUG(log, "on_accept, ec: {}", ec.message());
|
|
return;
|
|
}
|
|
LOG_INFO(log, "on_accept, peer = {}", peer_sock.remote_endpoint());
|
|
// async_read();
|
|
}
|
|
|
|
void main() noexcept override {
|
|
client_actor->make_request("/success-cancellable");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
REQUIRE(ee);
|
|
CHECK(ee->ec);
|
|
CHECK(ee->ec.message() != "");
|
|
peer_sock.cancel();
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_cancellation_3() {
|
|
struct F : fixture_t {
|
|
r::actor_ptr_t make_resolver() noexcept override {
|
|
struct sample_resolver_t : r::actor_base_t {
|
|
using r::actor_base_t::actor_base_t;
|
|
|
|
void configure(r::plugin::plugin_base_t &plugin) noexcept override {
|
|
r::actor_base_t::configure(plugin);
|
|
plugin.with_casted<r::plugin::address_maker_plugin_t>(
|
|
[&](auto &p) { p.set_identity(names::resolver, false); });
|
|
plugin.with_casted<r::plugin::registry_plugin_t>(
|
|
[&](auto &p) { p.register_name(names::resolver, get_address()); });
|
|
plugin.with_casted<r::plugin::starter_plugin_t>(
|
|
[&](auto &p) { p.subscribe_actor(&sample_resolver_t::on_request); });
|
|
}
|
|
|
|
void on_request(message::resolve_request_t &req) noexcept {
|
|
auto &sup = static_cast<supervisor_t &>(get_supervisor());
|
|
sup.fixture->client_actor->cancel_request();
|
|
}
|
|
};
|
|
return sup->create_actor<sample_resolver_t>().timeout(timeout).finish();
|
|
}
|
|
|
|
void main() noexcept override {
|
|
client_actor->make_request("/success-cancellable");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
REQUIRE(ee);
|
|
CHECK(ee->ec);
|
|
CHECK(ee->ec.message() != "");
|
|
acceptor.cancel();
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_cancellation_4() {
|
|
struct F : fixture_t {
|
|
void main() noexcept override {
|
|
client_actor->make_request("/success");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
message_opt_t handle_request(std::string_view path) noexcept override {
|
|
LOG_DEBUG(log, "cancelling request...");
|
|
client_actor->cancel_request();
|
|
sup->do_process();
|
|
return fixture_t::handle_request(path);
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
CHECK(ee);
|
|
CHECK(ee->ec);
|
|
CHECK(ee->ec.message() != "");
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_request_on_shutdown() {
|
|
struct F : fixture_t {
|
|
void main() noexcept override {
|
|
sup->send<payload::lock_t>(http_actor->get_address(), true);
|
|
http_actor->do_shutdown({});
|
|
sup->do_process();
|
|
|
|
client_actor->make_request("/success");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
REQUIRE(!ee);
|
|
|
|
auto &res = message.payload.res;
|
|
CHECK(res->response.result_int() == 200);
|
|
sup->send<payload::lock_t>(http_actor->get_address(), false);
|
|
sup->do_process();
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_shutdown_timeout() {
|
|
struct F : fixture_t {
|
|
void main() noexcept override {
|
|
sup->send<payload::lock_t>(http_actor->get_address(), true);
|
|
http_actor->do_shutdown({});
|
|
sup->do_process();
|
|
|
|
client_actor->make_request("/success");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
message_opt_t handle_request(std::string_view path) noexcept override { return {}; }
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
CHECK(ee);
|
|
CHECK(ee->ec);
|
|
CHECK(ee->ec == r::make_error_code(r::error_code_t::request_timeout));
|
|
acceptor.cancel();
|
|
}
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
void test_https_200_ok() {
|
|
namespace ssl = asio::ssl;
|
|
struct F : fixture_t {
|
|
using tcp_socket_t = tcp::socket;
|
|
using ssl_socket_t = ssl::stream<tcp::socket>;
|
|
using ssl_socket_opt_t = std::optional<ssl_socket_t>;
|
|
|
|
F() : ctx(boost::asio::ssl::context::tls), tmp_path{unique_path()}, tmp_guard{tmp_path} {
|
|
bfs::create_directories(tmp_path);
|
|
server_keys = utils::generate_pair("test_server").value();
|
|
auto cert_path = narrow((tmp_path / "cert.pem").wstring());
|
|
auto private_path = narrow((tmp_path / "priv.pem").wstring());
|
|
REQUIRE(server_keys.save(cert_path.data(), private_path.data()));
|
|
|
|
ssl_verify_store = cert_path;
|
|
|
|
ctx.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2);
|
|
|
|
auto &cert = server_keys.cert_data;
|
|
auto &key = server_keys.key_data;
|
|
ctx.use_certificate(asio::const_buffer(cert.data(), cert.size()), ssl::context::asn1);
|
|
ctx.use_private_key(asio::const_buffer(key.data(), key.size()), ssl::context::asn1);
|
|
}
|
|
|
|
utils::uri_ptr_t make_url(std::string_view path) override {
|
|
auto url_str = fmt::format("https://{}{}", listening_ep, path);
|
|
auto url = utils::parse(url_str);
|
|
LOG_DEBUG(log, "making request url {}", url);
|
|
return url;
|
|
}
|
|
|
|
void async_read() override {
|
|
LOG_TRACE(log, "async_read...");
|
|
auto handler = boost::beast::bind_front_handler(&fixture_t::on_read, this);
|
|
http::async_read(*peer_ssl_sock, rx_buff, request, std::move(handler));
|
|
}
|
|
|
|
void async_write(http::message_generator msg, bool keep_alive) noexcept override {
|
|
auto handler = boost::beast::bind_front_handler(&fixture_t::on_write, this, keep_alive);
|
|
boost::beast::async_write(*peer_ssl_sock, std::move(msg), std::move(handler));
|
|
}
|
|
|
|
void on_handshake(const sys::error_code &ec) noexcept {
|
|
LOG_INFO(log, "handshake, ec: {}", ec.message());
|
|
async_read();
|
|
}
|
|
|
|
void on_accept(const sys::error_code &ec) noexcept override {
|
|
LOG_INFO(log, "accept, ec: {}", ec.message());
|
|
auto ssl_sock = ssl_socket_t{std::move(peer_sock), ctx};
|
|
peer_ssl_sock = std::move(ssl_sock);
|
|
peer_ssl_sock->async_handshake(ssl::stream_base::server, [&](auto ec) { on_handshake(ec); });
|
|
}
|
|
|
|
void main() noexcept override {
|
|
client_actor->make_request("/success");
|
|
sup->do_process();
|
|
io_ctx.run();
|
|
}
|
|
|
|
void on_response(message::http_response_t &message) noexcept override {
|
|
LOG_DEBUG(log, "on_response");
|
|
auto &ee = message.payload.ee;
|
|
REQUIRE(!ee);
|
|
|
|
auto &res = message.payload.res;
|
|
CHECK(res->response.result_int() == 200);
|
|
}
|
|
|
|
utils::key_pair_t server_keys;
|
|
ssl::context ctx;
|
|
ssl_socket_opt_t peer_ssl_sock;
|
|
bfs::path tmp_path;
|
|
test::path_guard_t tmp_guard;
|
|
};
|
|
F().run();
|
|
}
|
|
|
|
int _init() {
|
|
test::init_logging();
|
|
REGISTER_TEST_CASE(test_http_start_and_shutdown, "test_http_start_and_shutdown", "[http]");
|
|
REGISTER_TEST_CASE(test_200_ok, "test_200_ok", "[http]");
|
|
REGISTER_TEST_CASE(test_200_keep_alive, "test_200_keep_alive", "[http]");
|
|
REGISTER_TEST_CASE(test_403_fail, "test_403_fail", "[http]");
|
|
REGISTER_TEST_CASE(test_garbage, "test_garbage", "[http]");
|
|
REGISTER_TEST_CASE(test_network_error, "test_network_error", "[http]");
|
|
REGISTER_TEST_CASE(test_response_timeout, "test_response_timeout", "[http]");
|
|
REGISTER_TEST_CASE(test_resolve_timeout, "test_resolve_timeout", "[http]");
|
|
REGISTER_TEST_CASE(test_resolve_fail, "test_resolve_fail", "[http]");
|
|
REGISTER_TEST_CASE(test_cancellation_1, "test_cancellation_1", "[http]");
|
|
REGISTER_TEST_CASE(test_cancellation_2, "test_cancellation_2", "[http]");
|
|
REGISTER_TEST_CASE(test_cancellation_3, "test_cancellation_3", "[http]");
|
|
REGISTER_TEST_CASE(test_cancellation_4, "test_cancellation_4", "[http]");
|
|
REGISTER_TEST_CASE(test_request_on_shutdown, "test_request_on_shutdown", "[http]");
|
|
REGISTER_TEST_CASE(test_shutdown_timeout, "test_shutdown_timeout", "[http]");
|
|
REGISTER_TEST_CASE(test_https_200_ok, "test_https_200_ok", "[http]");
|
|
return 1;
|
|
}
|
|
|
|
static int v = _init();
|